#include "sysconfig.h"
#include "statusline.h"
#include "hq2x_d3d.h"
#include "uae.h"
#include "dxwrap.h"
#include "win32gfx.h"
#include "win32gui.h"
#include "gfxfilter.h"
#include "direct3d.h"
#include "drawing.h"
#include "zfile.h"
#include "newcpu.h"
#include "win32.h"

// #if defined (D3D) && defined (GFXFILTER)

#define D3DXFX_LARGEADDRESS_HANDLE
#ifdef D3DXFX_LARGEADDRESS_HANDLE
    #define EFFECTCOMPILERFLAGS D3DXFX_LARGEADDRESSAWARE
#else
    #define EFFECTCOMPILERFLAGS 0
#endif

#define EFFECT_VERSION 2
#define D3DX9DLL L"d3dx9_43.dll"
#define TWOPASS 1

LPDIRECT3DTEXTURE9 Direct3D::CursorSurface = nullptr;

static TCHAR* D3DHEAD = L"-";
static int /*psEnabled,*/ psActive, psPreProcess /*, shaderon*/;

static D3DFORMAT tformat;
static int /*d3d_enabled,*/ d3d_ex;
static IDirect3D9* d3d;
static IDirect3D9Ex* d3dex;
static D3DPRESENT_PARAMETERS dpp;
static D3DDISPLAYMODEEX modeex;
static IDirect3DDevice9* d3ddev;
static IDirect3DDevice9Ex* d3ddevex;
static D3DSURFACE_DESC dsdbb;
static LPDIRECT3DTEXTURE9 texture /*, sltexture*/, ledtexture /*, masktexture, mask2texture, blanktexture*/;
static IDirect3DQuery9* query;
// static int masktexture_w, masktexture_h;
// static float mask2texture_w, mask2texture_h, mask2texture_ww, mask2texture_wh;
// static float mask2texture_multx, mask2texture_multy, mask2texture_offsetw;
static LPDIRECT3DTEXTURE9 lpWorkTexture1, lpWorkTexture2, lpTempTexture;
// LPDIRECT3DTEXTURE9 cursorsurfaced3d;
static LPDIRECT3DVOLUMETEXTURE9 lpHq2xLookupTexture;
static IDirect3DVertexBuffer9* vertexBuffer;
static ID3DXSprite* sprite;
static HWND d3dhwnd;
static int devicelost;
static int locked, fulllocked;
static int cursor_offset_x, cursor_offset_y;
// static float maskmult_x, maskmult_y;
// static RECT mask2rect;

static LPDIRECT3DSURFACE9 hdc_bb;

static D3DXMATRIXA16 m_matProj, m_matProj2;
static D3DXMATRIXA16 m_matWorld, m_matWorld2;
static D3DXMATRIXA16 m_matView, m_matView2;
static D3DXMATRIXA16 m_matPreProj;
static D3DXMATRIXA16 m_matPreView;
static D3DXMATRIXA16 m_matPreWorld;
static D3DXMATRIXA16 postproj;
static D3DXVECTOR4 maskmult, maskshift;

static int ledwidth, ledheight;
static int max_texture_w, max_texture_h;
static int tin_w, tin_h, tout_w, tout_h, window_h, window_w;
static int t_depth, mult /*, multx*/;
// static int required_sl_texture_w, required_sl_texture_h;
static int vsync2, guimode, maxscanline;
static int resetcount;
static int cursor_x, cursor_y, cursor_v;

static int can_shaders = -1;
static int good_enough = -1;

#define NUMVERTICES 8
#define D3DFVF_TLVERTEX D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1

struct TLVERTEX
{
    D3DXVECTOR3 position;       // vertex position
    D3DCOLOR diffuse;
    D3DXVECTOR2 texcoord;       // texture coords
};

static int ddraw_fs;
static LPDIRECTDRAW7 ddraw;

static void ddraw_fs_hack_free()
{
    HRESULT hr;

    if (!ddraw_fs)
        return;
    if (ddraw_fs == 2)
        ddraw->RestoreDisplayMode();
    hr = ddraw->SetCooperativeLevel(d3dhwnd, DDSCL_NORMAL);
    if (FAILED(hr))
        Logger::Write(L"IDirectDraw7_SetCooperativeLevel CLEAR: %s\n", DXError(hr));
    ddraw->Release();
    ddraw = nullptr;
    ddraw_fs = 0;
}

static int ddraw_fs_hack_init()
{
    HRESULT hr;
    struct MultiDisplay* md;

    ddraw_fs_hack_free();
    md = getdisplay(&g_curr_conf);
    if (!md)
        return 0;
    hr = DirectDrawCreateEx(md->primary ? nullptr : &md->guid, (LPVOID*)&ddraw, IID_IDirectDraw7, nullptr);
    if (FAILED(hr))
    {
        Logger::Write(L"DirectDrawCreateEx failed, %s\n", DXError(hr));
        return 0;
    }
    ddraw_fs = 1;
    hr = ddraw->SetCooperativeLevel(d3dhwnd, DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
    if (FAILED(hr))
    {
        Logger::Write(L"IDirectDraw7_SetCooperativeLevel SET: %s\n", DXError(hr));
        ddraw_fs_hack_free();
        return 0;
    }
    hr = ddraw->SetDisplayMode(dpp.BackBufferWidth, dpp.BackBufferHeight, t_depth, dpp.FullScreen_RefreshRateInHz, 0);
    if (FAILED(hr))
    {
        Logger::Write(L"IDirectDraw7_SetDisplayMode: %s\n", DXError(hr));
        ddraw_fs_hack_free();
        return 0;
    }
    ddraw_fs = 2;
    return 1;
}

static TCHAR* D3D_ErrorText(HRESULT error)
{
    return L"";
}
static TCHAR* D3D_ErrorString(HRESULT dival)
{
    static TCHAR dierr[200];
    _stprintf(dierr, L"%08X S=%d F=%04X C=%04X (%d) (%s)",
        dival, (dival & 0x80000000) ? 1 : 0,
        HRESULT_FACILITY(dival),
        HRESULT_CODE(dival),
        HRESULT_CODE(dival),
        D3D_ErrorText(dival));
    return dierr;
}

static D3DXMATRIX* MatrixOrthoOffCenterLH(D3DXMATRIXA16* pOut, float l, float r, float b, float t, float zn, float zf)
{
    pOut->_11 = 2.0f / r;
    pOut->_12 = 0.0f;
    pOut->_13 = 0.0f;
    pOut->_14 = 0.0f;
    pOut->_21 = 0.0f;
    pOut->_22 = 2.0f / t;
    pOut->_23 = 0.0f;
    pOut->_24 = 0.0f;
    pOut->_31 = 0.0f;
    pOut->_32 = 0.0f;
    pOut->_33 = 1.0f;
    pOut->_34 = 0.0f;
    pOut->_41 = -1.0f;
    pOut->_42 = -1.0f;
    pOut->_43 = 0.0f;
    pOut->_44 = 1.0f;
    return pOut;
}

static D3DXMATRIX* MatrixScaling(D3DXMATRIXA16* pOut, float sx, float sy, float sz)
{
    pOut->_11 = sx;
    pOut->_12 = 0.0f;
    pOut->_13 = 0.0f;
    pOut->_14 = 0.0f;
    pOut->_21 = 0.0f;
    pOut->_22 = sy;
    pOut->_23 = 0.0f;
    pOut->_24 = 0.0f;
    pOut->_31 = 0.0f;
    pOut->_32 = 0.0f;
    pOut->_33 = sz;
    pOut->_34 = 0.0f;
    pOut->_41 = 0.0f;
    pOut->_42 = 0.0f;
    pOut->_43 = 0.0f;
    pOut->_44 = 1.0f;
    return pOut;
}

static D3DXMATRIX* MatrixTranslation(D3DXMATRIXA16* pOut, float tx, float ty, float tz)
{
    pOut->_11 = 1.0f;
    pOut->_12 = 0.0f;
    pOut->_13 = 0.0f;
    pOut->_14 = 0.0f;
    pOut->_21 = 0.0f;
    pOut->_22 = 1.0f;
    pOut->_23 = 0.0f;
    pOut->_24 = 0.0f;
    pOut->_31 = 0.0f;
    pOut->_32 = 0.0f;
    pOut->_33 = 1.0f;
    pOut->_34 = 0.0f;
    pOut->_41 = tx;
    pOut->_42 = ty;
    pOut->_43 = tz;
    pOut->_44 = 1.0f;
    return pOut;
}

static TCHAR* D3DX_ErrorString(HRESULT hr, LPD3DXBUFFER Errors)
{
    static TCHAR buffer[1000];
    TCHAR* s = nullptr;

    buffer[0] = 0;
    if (Errors)
        s = Unicode::au((char*)Errors->GetBufferPointer());
    if (hr != S_OK)
        _tcscpy(buffer, D3D_ErrorString(hr));
    if (s)
    {
        if (buffer[0])
            _tcscat(buffer, L" ");
        _tcscat(buffer, s);
    }
    free(s);
    return buffer;
}

static int isd3d()
{
    if (devicelost || !d3ddev /* || !d3d_enabled */)
        return 0;
    return 1;
}

static LPD3DXEFFECT postEffect;
static D3DXHANDLE postSourceTextureHandle;
static D3DXHANDLE postMaskTextureHandle;
static D3DXHANDLE postTechnique, postTechniquePlain, postTechniqueAlpha;
static D3DXHANDLE postMatrixSource;
static D3DXHANDLE postMaskMult, postMaskShift;
static D3DXHANDLE postFilterMode;

static LPD3DXEFFECT pEffect;
static D3DXEFFECT_DESC EffectDesc;
static float m_scale;
static LPCSTR m_strName;
//  Matrix Handles
static D3DXHANDLE m_MatWorldEffectHandle;
static D3DXHANDLE m_MatViewEffectHandle;
static D3DXHANDLE m_MatProjEffectHandle;
static D3DXHANDLE m_MatWorldViewEffectHandle;
static D3DXHANDLE m_MatViewProjEffectHandle;
static D3DXHANDLE m_MatWorldViewProjEffectHandle;
//  Texture Handles
static D3DXHANDLE m_SourceDimsEffectHandle;
static D3DXHANDLE m_TexelSizeEffectHandle;
static D3DXHANDLE m_SourceTextureEffectHandle;
static D3DXHANDLE m_WorkingTexture1EffectHandle;
static D3DXHANDLE m_WorkingTexture2EffectHandle;
static D3DXHANDLE m_Hq2xLookupTextureHandle;
//  Technique stuff
static D3DXHANDLE m_PreprocessTechnique1EffectHandle;
static D3DXHANDLE m_PreprocessTechnique2EffectHandle;
static D3DXHANDLE m_CombineTechniqueEffectHandle;
enum psEffect_Pass { psEffect_None, psEffect_PreProcess1, psEffect_PreProcess2, psEffect_Combine };

static int postEffect_ParseParameters(LPD3DXEFFECTCOMPILER EffectCompiler, LPD3DXEFFECT effect)
{
    postSourceTextureHandle = effect->GetParameterByName(nullptr, "SourceTexture");
    postMaskTextureHandle = effect->GetParameterByName(nullptr, "OverlayTexture");
    postTechnique = effect->GetTechniqueByName("PostTechnique");
    postTechniquePlain = effect->GetTechniqueByName("PostTechniquePlain");
    postTechniqueAlpha = effect->GetTechniqueByName("PostTechniqueAlpha");
    postMatrixSource = effect->GetParameterByName(nullptr, "mtx");
    postMaskMult = effect->GetParameterByName(nullptr, "maskmult");
    postMaskShift = effect->GetParameterByName(nullptr, "maskshift");
    postFilterMode = effect->GetParameterByName(nullptr, "filtermode");
    if (!postMaskShift || !postMaskMult || !postFilterMode || !postMatrixSource)
    {
        gui_message(L"Mismatched _winuae.fx! Exiting..");
        abort();
    }
    return true;
}

static int psEffect_ParseParameters(LPD3DXEFFECTCOMPILER EffectCompiler, LPD3DXEFFECT effect)
{
    HRESULT hr = S_OK;
    //  Look at parameters for semantics and annotations that we know how to interpret
    D3DXPARAMETER_DESC ParamDesc;
    D3DXPARAMETER_DESC AnnotDesc;
    D3DXHANDLE hParam;
    D3DXHANDLE hAnnot;
    LPDIRECT3DBASETEXTURE9 pTex = nullptr;
    UINT iParam, iAnnot;

    if (effect == nullptr)
        return 0;

    for (iParam = 0; iParam < EffectDesc.Parameters; iParam++)
    {
        LPCSTR pstrName = nullptr;
        LPCSTR pstrFunction = nullptr;
        D3DXHANDLE pstrFunctionHandle = NULL;
        LPCSTR pstrTarget = nullptr;
        LPCSTR pstrTextureType = nullptr;
        INT Width = D3DX_DEFAULT;
        INT Height = D3DX_DEFAULT;
        INT Depth = D3DX_DEFAULT;

        hParam = effect->GetParameter(nullptr, iParam);
        hr = effect->GetParameterDesc(hParam, &ParamDesc);
        if (FAILED(hr))
        {
            Logger::Write(L"GetParameterDescParm(%d) failed: %s\n", D3DHEAD, iParam, D3DX_ErrorString(hr, nullptr));
            return 0;
        }
        hr = S_OK;
        if (ParamDesc.Semantic != nullptr)
        {
            if (ParamDesc.Class == D3DXPC_MATRIX_ROWS || ParamDesc.Class == D3DXPC_MATRIX_COLUMNS)
            {
                if (_strcmpi(ParamDesc.Semantic, "world") == 0)
                    m_MatWorldEffectHandle = hParam;
                else if (_strcmpi(ParamDesc.Semantic, "view") == 0)
                    m_MatViewEffectHandle = hParam;
                else if (_strcmpi(ParamDesc.Semantic, "projection") == 0)
                    m_MatProjEffectHandle = hParam;
                else if (_strcmpi(ParamDesc.Semantic, "worldview") == 0)
                    m_MatWorldViewEffectHandle = hParam;
                else if (_strcmpi(ParamDesc.Semantic, "viewprojection") == 0)
                    m_MatViewProjEffectHandle = hParam;
                else if (_strcmpi(ParamDesc.Semantic, "worldviewprojection") == 0)
                    m_MatWorldViewProjEffectHandle = hParam;
            }
            else if (ParamDesc.Class == D3DXPC_VECTOR && ParamDesc.Type == D3DXPT_FLOAT)
            {
                if (_strcmpi(ParamDesc.Semantic, "sourcedims") == 0)
                    m_SourceDimsEffectHandle = hParam;
                else if (_strcmpi(ParamDesc.Semantic, "texelsize") == 0)
                    m_TexelSizeEffectHandle = hParam;
            }
            else if (ParamDesc.Class == D3DXPC_SCALAR && ParamDesc.Type == D3DXPT_FLOAT)
            {
                if (_strcmpi(ParamDesc.Semantic, "SCALING") == 0)
                    hr = effect->GetFloat(hParam, &m_scale);
            }
            else if (ParamDesc.Class == D3DXPC_OBJECT && ParamDesc.Type == D3DXPT_TEXTURE)
            {
                if (_strcmpi(ParamDesc.Semantic, "SOURCETEXTURE") == 0)
                    m_SourceTextureEffectHandle = hParam;
                if (_strcmpi(ParamDesc.Semantic, "WORKINGTEXTURE") == 0)
                    m_WorkingTexture1EffectHandle = hParam;
                if (_strcmpi(ParamDesc.Semantic, "WORKINGTEXTURE1") == 0)
                    m_WorkingTexture2EffectHandle = hParam;
                if (_strcmpi(ParamDesc.Semantic, "HQ2XLOOKUPTEXTURE") == 0)
                    m_Hq2xLookupTextureHandle = hParam;
            }
            else if (ParamDesc.Class == D3DXPC_OBJECT && ParamDesc.Type == D3DXPT_STRING)
            {
                LPCSTR pstrTechnique = nullptr;

                if (_strcmpi(ParamDesc.Semantic, "COMBINETECHNIQUE") == 0)
                {
                    hr = effect->GetString(hParam, &pstrTechnique);
                    m_CombineTechniqueEffectHandle = effect->GetTechniqueByName(pstrTechnique);
                }
                else if (_strcmpi(ParamDesc.Semantic, "PREPROCESSTECHNIQUE") == 0)
                {
                    hr = effect->GetString(hParam, &pstrTechnique);
                    m_PreprocessTechnique1EffectHandle = effect->GetTechniqueByName(pstrTechnique);
                }
                else if (_strcmpi(ParamDesc.Semantic, "PREPROCESSTECHNIQUE1") == 0)
                {
                    hr = effect->GetString(hParam, &pstrTechnique);
                    m_PreprocessTechnique2EffectHandle = effect->GetTechniqueByName(pstrTechnique);
                }
                else if (_strcmpi(ParamDesc.Semantic, "NAME") == 0)
                {
                    hr = effect->GetString(hParam, &m_strName);
                }
            }
            if (FAILED(hr))
            {
                Logger::Write(L"ParamDesc.Semantic failed: %s\n", D3DHEAD, D3DX_ErrorString(hr, nullptr));
                return 0;
            }
        }

        for (iAnnot = 0; iAnnot < ParamDesc.Annotations; iAnnot++)
        {
            hAnnot = effect->GetAnnotation(hParam, iAnnot);
            hr = effect->GetParameterDesc(hAnnot, &AnnotDesc);
            if (FAILED(hr))
            {
                Logger::Write(L"GetParameterDescAnnot(%d) failed: %s\n", D3DHEAD, iAnnot, D3DX_ErrorString(hr, nullptr));
                return 0;
            }
            hr = S_OK;
            if (_strcmpi(AnnotDesc.Name, "name") == 0)
                hr = effect->GetString(hAnnot, &pstrName);
            else if (_strcmpi(AnnotDesc.Name, "function") == 0)
            {
                hr = effect->GetString(hAnnot, &pstrFunction);
                pstrFunctionHandle = effect->GetFunctionByName(pstrFunction);
            }
            else if (_strcmpi(AnnotDesc.Name, "target") == 0)
                hr = effect->GetString(hAnnot, &pstrTarget);
            else if (_strcmpi(AnnotDesc.Name, "width") == 0)
                hr = effect->GetInt(hAnnot, &Width);
            else if (_strcmpi(AnnotDesc.Name, "height") == 0)
                hr = effect->GetInt(hAnnot, &Height);
            else if (_strcmpi(AnnotDesc.Name, "depth") == 0)
                hr = effect->GetInt(hAnnot, &Depth);
            else if (_strcmpi(AnnotDesc.Name, "type") == 0)
                hr = effect->GetString(hAnnot, &pstrTextureType);
            if (FAILED(hr))
            {
                Logger::Write(L"GetString/GetInt(%d) failed: %s\n", D3DHEAD, iAnnot, D3DX_ErrorString(hr, nullptr));
                return 0;
            }
        }

        if (pstrFunctionHandle != NULL)
        {
            LPD3DXBUFFER pTextureShader = nullptr;
            LPD3DXBUFFER lpErrors = 0;

            if (pstrTarget == nullptr || strcmp(pstrTarget, "tx_1_1"))
                pstrTarget = "tx_1_0";

            if (SUCCEEDED(hr = EffectCompiler->CompileShader(
                        pstrFunctionHandle, pstrTarget, D3DXSHADER_SKIPVALIDATION | D3DXSHADER_DEBUG, &pTextureShader, &lpErrors, nullptr)))
            {
                LPD3DXTEXTURESHADER ppTextureShader;
                if (lpErrors)
                    lpErrors->Release();

                if (Width == D3DX_DEFAULT)
                    Width = 64;
                if (Height == D3DX_DEFAULT)
                    Height = 64;
                if (Depth == D3DX_DEFAULT)
                    Depth = 64;

                D3DXCreateTextureShader((DWORD*)pTextureShader->GetBufferPointer(), &ppTextureShader);

                if (pstrTextureType != nullptr)
                {
                    if (_strcmpi(pstrTextureType, "volume") == 0)
                    {
                        LPDIRECT3DVOLUMETEXTURE9 pVolumeTex = nullptr;
                        if (SUCCEEDED(hr = D3DXCreateVolumeTexture(d3ddev,
                                    Width, Height, Depth, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &pVolumeTex)))
                        {
                            if (SUCCEEDED(hr = D3DXFillVolumeTextureTX(pVolumeTex, ppTextureShader)))
                            {
                                pTex = pVolumeTex;
                            }
                        }
                    }
                    else if (_strcmpi(pstrTextureType, "cube") == 0)
                    {
                        LPDIRECT3DCUBETEXTURE9 pCubeTex = nullptr;
                        if (SUCCEEDED(hr = D3DXCreateCubeTexture(d3ddev,
                                    Width, D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &pCubeTex)))
                        {
                            if (SUCCEEDED(hr = D3DXFillCubeTextureTX(pCubeTex, ppTextureShader)))
                            {
                                pTex = pCubeTex;
                            }
                        }
                    }
                }
                else
                {
                    LPDIRECT3DTEXTURE9 p2DTex = nullptr;
                    if (SUCCEEDED(hr = D3DXCreateTexture(d3ddev, Width, Height,
                                D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &p2DTex)))
                    {
                        if (SUCCEEDED(hr = D3DXFillTextureTX(p2DTex, ppTextureShader)))
                        {
                            pTex = p2DTex;
                        }
                    }
                }
                effect->SetTexture(effect->GetParameter(nullptr, iParam), pTex);
                if (pTex)
                    pTex->Release();
                if (pTextureShader)
                    pTextureShader->Release();
                if (ppTextureShader)
                    ppTextureShader->Release();
            }
            else
            {
                Logger::Write(L"%s: Could not compile texture shader: %s\n", D3DHEAD, D3DX_ErrorString(hr, lpErrors));
                if (lpErrors)
                    lpErrors->Release();
                return 0;
            }
        }
    }
    return 1;
}

static int psEffect_hasPreProcess()
{
    return m_PreprocessTechnique1EffectHandle != 0;
}
static int psEffect_hasPreProcess2()
{
    return m_PreprocessTechnique2EffectHandle != 0;
}

int Direct3D::GoodEnough()
{
    // static int d3d_good;
    // LPDIRECT3D9 d3dx;
    // D3DCAPS9 d3dCaps;

    if (good_enough != -1)
        return good_enough > 0;

    good_enough = 0;

    LPDIRECT3D9 d3dx = Direct3DCreate9(D3D_SDK_VERSION);
    if (d3dx != nullptr)
    {
        D3DCAPS9 d3dCaps;
        if (SUCCEEDED(d3dx->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dCaps)))
        {
            if (d3dCaps.PixelShaderVersion >= D3DPS_VERSION(1, 0))
            {
                if (((d3dCaps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL) == D3DPTEXTURECAPS_NONPOW2CONDITIONAL) || !(d3dCaps.TextureCaps & D3DPTEXTURECAPS_POW2))
                {
                    if (!(d3dCaps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY) && (d3dCaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES))
                    {
                        good_enough = 1;
                        // shaderon = 1;
                    }
                }
            }
        }
        d3dx->Release();
    }
    return good_enough > 0;
}

bool Direct3D::CanShaders()
{
    // static int d3d_yesno = 0;
    // HMODULE h;
    // LPDIRECT3D9 d3dx;
    // D3DCAPS9 d3dCaps;

    if (can_shaders != -1)
        return can_shaders > 0;

    can_shaders = 0;

    HMODULE h = LoadLibrary(D3DX9DLL);
    if (h != nullptr)
    {
        FreeLibrary(h);
        LPDIRECT3D9 d3dx = Direct3DCreate9(D3D_SDK_VERSION);
        if (d3dx != nullptr)
        {
            D3DCAPS9 d3dCaps;
            if (SUCCEEDED(d3dx->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dCaps)))
            {
                if (d3dCaps.PixelShaderVersion >= D3DPS_VERSION(2, 0))
                {
                    if (Logging::DIRECT3D_DEBUG)
                        Logger::Write(L"Direct3D: Pixel shader 2.0+ support detected, shader filters enabled.\n");
                    can_shaders = 1;
                }
            }
            d3dx->Release();
        }
    }
    return can_shaders > 0;
}

class ShadersFileInclude : public ID3DXInclude
{
    public:

        CMapStringToPtr m_includes;
        CMap<LPCSTR, LPCSTR, CString, CString&>m_inv;

        STDMETHOD(Open) (D3DXINCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData,
        LPCVOID * ppData, UINT * pBytes)
        {
            void* ptr;
            CString filename = pFileName;
            if (!m_includes.Lookup(filename, ptr))
            {
                CStringA* code = new CStringA();

                CString filename = CString(pFileName);
                filename.MakeLower();

                *code = CLR::Shaders::GetShaderCode(filename);

                if (*code == "")
                    Logger::Write(L"%s: Can't find shader include: %s", D3DHEAD, CString(pFileName));

                m_includes.SetAt(filename, code);
                ptr = code;

                m_inv.SetAt((LPCSTR)(*code), filename);
            }

            CStringA* code = (CStringA*)ptr;

            *ppData = (LPCSTR)(*code);
            *pBytes = code->GetLength();

            return S_OK;
        }

        STDMETHOD(Close) (LPCVOID pData)
        {
            CString key;
            m_inv.Lookup((LPCSTR)pData, key);
            void* ptr;
            m_includes.Lookup(key, ptr);
            CStringA* code = (CStringA*)ptr;
            delete code;
            m_inv.RemoveKey((LPCSTR)pData);
            m_includes.RemoveKey(key);

            return S_OK;
        }
};

// static const char* fx10 = {
//
//     "// 2 (version)\n"
//     "//\n"
//     "// WinUAE Direct3D post processing shader\n"
//     "//\n"
//     "// by Toni Wilen 2010\n"
//     "\n"
//     "uniform extern float4x4 mtx;\n"
//     "uniform extern float2 maskmult;\n"
//     "uniform extern float2 maskshift;\n"
//     "uniform extern int filtermode;\n"
//     "\n"
//     "// final possibly filtered Amiga output\n"
//     "texture SourceTexture : SOURCETEXTURE;\n"
//     "\n"
//     "sampler	SourceSampler = sampler_state {\n"
//     "	Texture	  = (SourceTexture);\n"
//     "	MinFilter = POINT;\n"
//     "	MagFilter = POINT;\n"
//     "	MipFilter = NONE;\n"
//     "	AddressU  = Clamp;\n"
//     "	AddressV  = Clamp;\n"
//     "};\n"
//     "\n"
//     "\n"
//     "texture OverlayTexture : OVERLAYTEXTURE;\n"
//     "\n"
//     "sampler	OverlaySampler = sampler_state {\n"
//     "	Texture	  = (OverlayTexture);\n"
//     "	MinFilter = POINT;\n"
//     "	MagFilter = POINT;\n"
//     "	MipFilter = NONE;\n"
//     "	AddressU  = Wrap;\n"
//     "	AddressV  = Wrap;\n"
//     "};\n"
//     "\n"
//     "struct VS_OUTPUT_POST\n"
//     "{\n"
//     "	float4 Position		: POSITION;\n"
//     "	float2 CentreUV		: TEXCOORD0;\n"
//     "	float2 Selector		: TEXCOORD1;\n"
//     "};\n"
//     "\n"
//     "VS_OUTPUT_POST VS_Post(float3 pos : POSITION, float2 TexCoord : TEXCOORD0)\n"
//     "{\n"
//     "	VS_OUTPUT_POST Out = (VS_OUTPUT_POST)0;\n"
//     "\n"
//     "	Out.Position = mul(float4(pos, 1.0f), mtx);\n"
//     "	Out.CentreUV = TexCoord;\n"
//     "	Out.Selector = TexCoord * maskmult + maskshift;\n"
//     "	return Out;\n"
//     "}\n"
//     "\n"
//     "float4 PS_Post(in VS_OUTPUT_POST inp) : COLOR\n"
//     "{\n"
//     "	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
//     "	float4 o = tex2D(OverlaySampler, inp.Selector);\n"
//     "	return s * o;\n"
//     "}\n"
//     "\n"
//     "float4 PS_PostAlpha(in VS_OUTPUT_POST inp) : COLOR\n"
//     "{\n"
//     "	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
//     "	float4 o = tex2D(OverlaySampler, inp.Selector);\n"
//     "	return s * (1 - o.a) + (o * o.a);\n"
//     "}\n"
//     "\n"
//     "float4 PS_PostPlain(in VS_OUTPUT_POST inp) : COLOR\n"
//     "{\n"
//     "	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
//     "	return s;\n"
//     "}\n"
//     "\n"
//     "// source and overlay texture\n"
//     "technique PostTechnique\n"
//     "{\n"
//     "    pass P0\n"
//     "    {\n"
//     "		VertexShader = compile vs_1_0 VS_Post();\n"
//     "		PixelShader  = compile ps_1_0 PS_Post();\n"
//     "    }  \n"
//     "}\n"
//     "\n"
//     "// source and scanline texture with alpha\n"
//     "technique PostTechniqueAlpha\n"
//     "{\n"
//     "	pass P0\n"
//     "	{\n"
//     "		VertexShader = compile vs_1_0 VS_Post();\n"
//     "		PixelShader  = compile ps_1_0 PS_PostAlpha();\n"
//     "    } \n"
//     "}\n"
//     "\n"
//     "// only source texture\n"
//     "technique PostTechniquePlain\n"
//     "{\n"
//     "	pass P0\n"
//     "	{\n"
//     "		VertexShader = compile vs_1_0 VS_Post();\n"
//     "		PixelShader  = compile ps_1_0 PS_PostPlain();\n"
//     "    }\n"
//     "}\n"
// };

// static const char* fx20 = {
//
//     "// 2 (version)\n"
//     "//\n"
//     "// WinUAE Direct3D post processing shader\n"
//     "//\n"
//     "// by Toni Wilen 2010\n"
//     "\n"
//     "uniform extern float4x4 mtx;\n"
//     "uniform extern float2 maskmult;\n"
//     "uniform extern float2 maskshift;\n"
//     "uniform extern int filtermode;\n"
//     "\n"
//     "// final possibly filtered Amiga output\n"
//     "texture SourceTexture : SOURCETEXTURE;\n"
//     "\n"
//     "sampler	SourceSampler = sampler_state {\n"
//     "	Texture	  = (SourceTexture);\n"
//     "	MinFilter = filtermode;\n"
//     "	MagFilter = filtermode;\n"
//     "	MipFilter = NONE;\n"
//     "	AddressU  = Clamp;\n"
//     "	AddressV  = Clamp;\n"
//     "};\n"
//     "\n"
//     "\n"
//     "texture OverlayTexture : OVERLAYTEXTURE;\n"
//     "\n"
//     "sampler	OverlaySampler = sampler_state {\n"
//     "	Texture	  = (OverlayTexture);\n"
//     "	MinFilter = POINT;\n"
//     "	MagFilter = POINT;\n"
//     "	MipFilter = NONE;\n"
//     "	AddressU  = Wrap;\n"
//     "	AddressV  = Wrap;\n"
//     "};\n"
//     "\n"
//     "struct VS_OUTPUT_POST\n"
//     "{\n"
//     "	float4 Position		: POSITION;\n"
//     "	float2 CentreUV		: TEXCOORD0;\n"
//     "	float2 Selector		: TEXCOORD1;\n"
//     "};\n"
//     "\n"
//     "VS_OUTPUT_POST VS_Post(float3 pos : POSITION, float2 TexCoord : TEXCOORD0)\n"
//     "{\n"
//     "	VS_OUTPUT_POST Out = (VS_OUTPUT_POST)0;\n"
//     "\n"
//     "	Out.Position = mul(float4(pos, 1.0f), mtx);\n"
//     "	Out.CentreUV = TexCoord;\n"
//     "	Out.Selector = TexCoord * maskmult + maskshift;\n"
//     "	return Out;\n"
//     "}\n"
//     "\n"
//     "float4 PS_Post(in VS_OUTPUT_POST inp) : COLOR\n"
//     "{\n"
//     "	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
//     "	float4 o = tex2D(OverlaySampler, inp.Selector);\n"
//     "	return s * o;\n"
//     "}\n"
//     "\n"
//     "float4 PS_PostAlpha(in VS_OUTPUT_POST inp) : COLOR\n"
//     "{\n"
//     "	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
//     "	float4 o = tex2D(OverlaySampler, inp.Selector);\n"
//     "	return s * (1 - o.a) + (o * o.a);\n"
//     "}\n"
//     "\n"
//     "float4 PS_PostPlain(in VS_OUTPUT_POST inp) : COLOR\n"
//     "{\n"
//     "	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
//     "	return s;\n"
//     "}\n"
//     "\n"
//     "// source and overlay texture\n"
//     "technique PostTechnique\n"
//     "{\n"
//     "    pass P0\n"
//     "    {\n"
//     "		VertexShader = compile vs_1_0 VS_Post();\n"
//     "		PixelShader  = compile ps_2_0 PS_Post();\n"
//     "    }  \n"
//     "}\n"
//     "\n"
//     "// source and scanline texture with alpha\n"
//     "technique PostTechniqueAlpha\n"
//     "{\n"
//     "	pass P0\n"
//     "	{\n"
//     "		VertexShader = compile vs_1_0 VS_Post();\n"
//     "		PixelShader  = compile ps_2_0 PS_PostAlpha();\n"
//     "    } \n"
//     "}\n"
//     "\n"
//     "// only source texture\n"
//     "technique PostTechniquePlain\n"
//     "{\n"
//     "	pass P0\n"
//     "	{\n"
//     "		VertexShader = compile vs_1_0 VS_Post();\n"
//     "		PixelShader  = compile ps_2_0 PS_PostPlain();\n"
//     "    }\n"
//     "}\n"
// };

static LPD3DXEFFECT psEffect_LoadEffect(const TCHAR* a_shaderName, bool a_postEffect)
{
    int ret = 0;
    LPD3DXEFFECTCOMPILER EffectCompiler = nullptr;
    LPD3DXBUFFER Errors = nullptr;
    LPD3DXBUFFER BufferEffect = nullptr;
    // HRESULT hr;
    // TCHAR tmp[MAX_DPATH], tmp2[MAX_DPATH];
    LPD3DXEFFECT effect = nullptr;
    // static int first;
    // DWORD compileflags = psEnabled ? 0 : D3DXSHADER_USE_LEGACY_D3DX9_31_DLL;
    // int canusefile = 0, existsfile = 0;
    // bool plugin_path;
    // compileflags |= EFFECTCOMPILERFLAGS;

    // plugin_path = get_plugin_path(tmp, sizeof tmp / sizeof(TCHAR), L"filtershaders\\direct3d");

    if (Logging::DIRECT3D_DEBUG)
        Logger::Write(L"%s: Attempting to load '%s'\n", D3DHEAD, a_shaderName);

    ShadersFileInclude include;
    CStringA code = CLR::Shaders::GetShaderCode(a_shaderName);

    HRESULT hr = D3DXCreateEffectCompiler(code, code.GetLength(), nullptr, &include, EFFECTCOMPILERFLAGS,
        &EffectCompiler, &Errors);

    if (FAILED(hr))
    {
        Logger::Write(L"%s: D3DXCreateEffectCompiler failed: %s\n", D3DHEAD, D3DX_ErrorString(hr, Errors));
        goto end;
    }

    if (Errors)
    {
        Logger::Write(L"%s: '%s' warning: %s\n", D3DHEAD, a_shaderName, D3DX_ErrorString(hr, Errors));
        Errors->Release();
        Errors = nullptr;
    }

    hr = EffectCompiler->CompileEffect(0, &BufferEffect, &Errors);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: CompileEffect failed: %s\n", D3DHEAD, D3DX_ErrorString(hr, Errors));
        goto end;
    }
    void* bp = BufferEffect->GetBufferPointer();
    int bplen = BufferEffect->GetBufferSize();
    hr = D3DXCreateEffect(d3ddev,
        bp, bplen,
        nullptr, nullptr,
        EFFECTCOMPILERFLAGS,
        nullptr, &effect, &Errors);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: D3DXCreateEffect failed: %s\n", D3DHEAD, D3DX_ErrorString(hr, Errors));
        goto end;
    }
    hr = effect->GetDesc(&EffectDesc);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: effect->GetDesc() failed: %s\n", D3DHEAD, D3DX_ErrorString(hr, Errors));
        goto end;
    }

    if (!a_postEffect)
    {
        if (!psEffect_ParseParameters(EffectCompiler, effect))
            goto end;
    }
    else
    {
        if (!postEffect_ParseParameters(EffectCompiler, effect))
            goto end;
    }
    ret = 1;

end:

    if (Errors)
        Errors->Release();
    if (BufferEffect)
        BufferEffect->Release();
    if (EffectCompiler)
        EffectCompiler->Release();

    if (!a_postEffect)
    {
        psActive = FALSE;
        psPreProcess = FALSE;
        if (ret)
        {
            psActive = TRUE;
            if (psEffect_hasPreProcess())
                psPreProcess = TRUE;
        }
    }
    if (ret)
    {
        if (Logging::DIRECT3D_DEBUG)
            Logger::Write(L"%s: pixelshader filter '%s' enabled\n", D3DHEAD, a_shaderName);
    }
    else
        Logger::Write(L"%s: pixelshader filter '%s' failed to initialize\n", D3DHEAD, a_shaderName);

    return effect;
}

static int psEffect_SetMatrices(D3DXMATRIXA16* matProj, D3DXMATRIXA16* matView, D3DXMATRIXA16* matWorld)
{
    HRESULT hr;

    if (m_MatWorldEffectHandle)
    {
        hr = pEffect->SetMatrix(m_MatWorldEffectHandle, matWorld);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: Create:SetMatrix:matWorld %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    if (m_MatViewEffectHandle)
    {
        hr = pEffect->SetMatrix(m_MatViewEffectHandle, matView);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: Create:SetMatrix:matView %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    if (m_MatProjEffectHandle)
    {
        hr = pEffect->SetMatrix(m_MatProjEffectHandle, matProj);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: Create:SetMatrix:matProj %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    if (m_MatWorldViewEffectHandle)
    {
        D3DXMATRIXA16 matWorldView;
        D3DXMatrixMultiply(&matWorldView, matWorld, matView);
        hr = pEffect->SetMatrix(m_MatWorldViewEffectHandle, &matWorldView);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: Create:SetMatrix:matWorldView %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    if (m_MatViewProjEffectHandle)
    {
        D3DXMATRIXA16 matViewProj;
        D3DXMatrixMultiply(&matViewProj, matView, matProj);
        hr = pEffect->SetMatrix(m_MatViewProjEffectHandle, &matViewProj);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: Create:SetMatrix:matViewProj %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    if (m_MatWorldViewProjEffectHandle)
    {
        D3DXMATRIXA16 tmp, matWorldViewProj;
        D3DXMatrixMultiply(&tmp, matWorld, matView);
        D3DXMatrixMultiply(&matWorldViewProj, &tmp, matProj);
        hr = pEffect->SetMatrix(m_MatWorldViewProjEffectHandle, &matWorldViewProj);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: Create:SetMatrix:matWorldViewProj %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    return 1;
}

static int psEffect_SetTextures(LPDIRECT3DTEXTURE9 lpSource, LPDIRECT3DTEXTURE9 lpWorking1,
    LPDIRECT3DTEXTURE9 lpWorking2, LPDIRECT3DVOLUMETEXTURE9 lpHq2xLookupTexture)
{
    HRESULT hr;
    D3DXVECTOR4 fDims, fTexelSize;

    if (!m_SourceTextureEffectHandle)
    {
        Logger::Write(L"%s: Texture with SOURCETEXTURE semantic not found\n", D3DHEAD);
        return 0;
    }
    hr = pEffect->SetTexture(m_SourceTextureEffectHandle, lpSource);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: SetTextures:lpSource %s\n", D3DHEAD, D3D_ErrorString(hr));
        return 0;
    }
    if (m_WorkingTexture1EffectHandle)
    {
        hr = pEffect->SetTexture(m_WorkingTexture1EffectHandle, lpWorking1);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: SetTextures:lpWorking1 %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    if (m_WorkingTexture2EffectHandle)
    {
        hr = pEffect->SetTexture(m_WorkingTexture2EffectHandle, lpWorking2);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: SetTextures:lpWorking2 %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    if (m_Hq2xLookupTextureHandle)
    {
        hr = pEffect->SetTexture(m_Hq2xLookupTextureHandle, lpHq2xLookupTexture);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: SetTextures:lpHq2xLookupTexture %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    fDims.x = 256;
    fDims.y = 256;
    fDims.z = 1;
    fDims.w = 1;
    fTexelSize.x = 1;
    fTexelSize.y = 1;
    fTexelSize.z = 1;
    fTexelSize.w = 1;
    if (lpSource)
    {
        D3DSURFACE_DESC Desc;
        lpSource->GetLevelDesc(0, &Desc);
        fDims.x = (FLOAT)Desc.Width;
        fDims.y = (FLOAT)Desc.Height;
    }
    fTexelSize.x = 1 / fDims.x;
    fTexelSize.y = 1 / fDims.y;
    if (m_SourceDimsEffectHandle)
    {
        hr = pEffect->SetVector(m_SourceDimsEffectHandle, &fDims);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: SetTextures:SetVector:Source %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }
    if (m_TexelSizeEffectHandle)
    {
        hr = pEffect->SetVector(m_TexelSizeEffectHandle, &fTexelSize);
        if (FAILED(hr))
        {
            Logger::Write(L"%s: SetTextures:SetVector:Texel %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
    }

    return 1;
}

static int psEffect_Begin(LPD3DXEFFECT effect, enum psEffect_Pass pass, UINT* pPasses)
{
    HRESULT hr;
    switch (pass)
    {
        case psEffect_PreProcess1:
            hr = effect->SetTechnique(m_PreprocessTechnique1EffectHandle);
            break;
        case psEffect_PreProcess2:
            hr = effect->SetTechnique(m_PreprocessTechnique2EffectHandle);
            break;
        case psEffect_Combine:
            hr = effect->SetTechnique(m_CombineTechniqueEffectHandle);
            break;
        default:
            hr = S_OK;
            break;
    }
    if (FAILED(hr))
    {
        Logger::Write(L"%s: SetTechnique: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return 0;
    }
    hr = effect->Begin(pPasses, 0);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: Begin: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return 0;
    }
    return 1;
}

static int psEffect_BeginPass(LPD3DXEFFECT effect, UINT Pass)
{
    HRESULT hr;

    hr = effect->BeginPass(Pass);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: BeginPass: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return 0;
    }
    return 1;
}
static int psEffect_EndPass(LPD3DXEFFECT effect)
{
    HRESULT hr;

    hr = effect->EndPass();
    if (FAILED(hr))
    {
        Logger::Write(L"%s: EndPass: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return 0;
    }
    return 1;
}
static int psEffect_End(LPD3DXEFFECT effect)
{
    HRESULT hr;

    hr = effect->End();
    if (FAILED(hr))
    {
        Logger::Write(L"%s: End: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return 0;
    }
    return 1;
}

static LPDIRECT3DTEXTURE9 createtext(int w, int h, D3DFORMAT format)
{
    LPDIRECT3DTEXTURE9 t;
    D3DLOCKED_RECT locked;
    HRESULT hr;

    hr = d3ddev->CreateTexture(w, h, 1, D3DUSAGE_DYNAMIC, format, D3DPOOL_DEFAULT, &t, nullptr);
    if (FAILED(hr))
        Logger::Write(L"%s: CreateTexture() D3DUSAGE_DYNAMIC failed: %s (%d*%d %08x)\n",
            D3DHEAD, D3D_ErrorString(hr), w, h, format);
    if (FAILED(hr))
    {
        hr = d3ddev->CreateTexture(w, h, 1, 0, format, D3DPOOL_DEFAULT, &t, nullptr);
    }
    if (FAILED(hr))
    {
        Logger::Write(L"%s: CreateTexture() failed: %s (%d*%d %08x)\n",
            D3DHEAD, D3D_ErrorString(hr), w, h, format);
        return 0;
    }
    hr = t->LockRect(0, &locked, nullptr, 0);
    if (SUCCEEDED(hr))
    {
        int y;
        int wb;
        wb = w * 4;
        if (wb > locked.Pitch)
            wb = w * 2;
        if (wb > locked.Pitch)
            wb = w * 1;
        for (y = 0; y < h; y++)
            memset((byte*)locked.pBits + y * locked.Pitch, 0, wb);
        t->UnlockRect(0);
    }
    return t;
}

static int createtexture(int iw, int ih, int ow, int oh, int win_w, int win_h)
{
    HRESULT hr;

    texture = createtext(iw, ih, tformat);
    if (!texture)
        return 0;

    if (Logging::DIRECT3D_DEBUG)
        Logger::Write(L"%s: %d*%d texture allocated, bits per pixel %d\n", D3DHEAD, iw, ih, t_depth);

    int w, h;
    if (ow > win_w && oh > win_h)
    {
        w = ow;
        h = oh;
    }
    else
    {
        w = win_w;
        h = win_h;
    }
    if (FAILED(hr = d3ddev->CreateTexture(w, h, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &lpTempTexture, nullptr)))
    {
        Logger::Write(L"%s: Failed to create working texture1: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return 0;
    }

    if (Logging::DIRECT3D_DEBUG)
        Logger::Write(L"%s: %d*%d working texture allocated, bits per pixel %d\n", D3DHEAD, w, h, t_depth);

    if (psActive)
    {
        D3DLOCKED_BOX lockedBox;
        if (FAILED(hr = d3ddev->CreateTexture(iw, ih, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &lpWorkTexture1, nullptr)))
        {
            Logger::Write(L"%s: Failed to create temp texture: %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
        if (FAILED(hr = d3ddev->CreateTexture(iw, ih, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &lpWorkTexture2, nullptr)))
        {
            Logger::Write(L"%s: Failed to create working texture2: %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
        if (FAILED(hr = d3ddev->CreateVolumeTexture(256, 16, 256, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &lpHq2xLookupTexture, nullptr)))
        {
            Logger::Write(L"%s: Failed to create volume texture: %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
        if (FAILED(hr = lpHq2xLookupTexture->LockBox(0, &lockedBox, nullptr, 0)))
        {
            Logger::Write(L"%s: Failed to lock box of volume texture: %s\n", D3DHEAD, D3D_ErrorString(hr));
            return 0;
        }
        BuildHq2xLookupTexture(w, h, iw, ih, (unsigned char*)lockedBox.pBits);
        lpHq2xLookupTexture->UnlockBox(0);
    }
    return 1;
}

static void updateleds()
{
    D3DLOCKED_RECT locked;
    HRESULT hr;
    static uint rc[256], gc[256], bc[256], a[256];
    static int done;
    int i, y;

    if (!done)
    {
        for (i = 0; i < 256; i++)
        {
            rc[i] = i << 16;
            gc[i] = i << 8;
            bc[i] = i << 0;
            a[i] = i << 24;
        }
        done = 1;
    }
    hr = ledtexture->LockRect(0, &locked, nullptr, D3DLOCK_DISCARD);
    if (FAILED(hr))
    {
        Logger::Write(L"%d: SL LockRect failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return;
    }
    for (y = 0; y < TD_TOTAL_HEIGHT; y++)
    {
        byte* buf = (byte*)locked.pBits + y * locked.Pitch;
        memset(buf, 0, ledwidth * 4);
        draw_status_line_single(buf, 32 / 8, y, ledwidth, rc, gc, bc, a);
    }
    ledtexture->UnlockRect(0);
}

static int createledtexture()
{
    ledwidth = window_w;
    ledheight = TD_TOTAL_HEIGHT;
    ledtexture = createtext(ledwidth, ledheight, D3DFMT_A8R8G8B8);
    if (!ledtexture)
        return 0;
    return 1;
}

// static int createsltexture()
// {
//     if (masktexture)
//         return 0;
//     sltexture = createtext(required_sl_texture_w, required_sl_texture_h, t_depth < 32 ? D3DFMT_A4R4G4B4 : D3DFMT_A8R8G8B8);
//     if (!sltexture)
//         return 0;
//     Logger::Write(L"%s: SL %d*%d texture allocated\n", D3DHEAD, required_sl_texture_w, required_sl_texture_h);
//     maskmult_x = 1.0;
//     maskmult_y = 1.0;
//     return 1;
// }

// static void createscanlines(int force)
// {
//     HRESULT hr;
//     D3DLOCKED_RECT locked;
//     static int osl1, osl2, osl3;
//     int sl4, sl42;
//     int l1, l2;
//     int x, y, yy;
//     byte* sld, * p;
//     int bpp;
//
//     if (osl1 == g_curr_conf.gfx_filter_scanlines && osl3 == g_curr_conf.gfx_filter_scanlinelevel && osl2 == g_curr_conf.gfx_filter_scanlineratio && !force)
//         return;
//     bpp = t_depth < 32 ? 2 : 4;
//     osl1 = g_curr_conf.gfx_filter_scanlines;
//     osl3 = g_curr_conf.gfx_filter_scanlinelevel;
//     osl2 = g_curr_conf.gfx_filter_scanlineratio;
//     sl4 = g_curr_conf.gfx_filter_scanlines * 16 / 100;
//     sl42 = g_curr_conf.gfx_filter_scanlinelevel * 16 / 100;
//     if (sl4 > 15)
//         sl4 = 15;
//     if (sl42 > 15)
//         sl42 = 15;
//     l1 = (g_curr_conf.gfx_filter_scanlineratio >> 0) & 15;
//     l2 = (g_curr_conf.gfx_filter_scanlineratio >> 4) & 15;
//
//     if (l1 + l2 <= 0)
//         return;
//
//     if (!sltexture)
//     {
//         if (osl1 == 0 && osl3 == 0)
//             return;
//         if (!createsltexture())
//             return;
//     }
//
//     hr = sltexture->LockRect(0, &locked, nullptr, 0);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"%s: SL LockRect failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
//         return;
//     }
//     sld = (byte*)locked.pBits;
//     for (y = 0; y < required_sl_texture_h; y++)
//         memset(sld + y * locked.Pitch, 0, required_sl_texture_w * bpp);
//     for (y = 1; y < required_sl_texture_h; y += l1 + l2)
//     {
//         for (yy = 0; yy < l2 && y + yy < required_sl_texture_h; yy++)
//         {
//             for (x = 0; x < required_sl_texture_w; x++)
//             {
//                 byte sll = sl42;
//                 p = &sld[(y + yy) * locked.Pitch + (x * bpp)];
//                 if (bpp < 4)
//                 {
//                     /* 16-bit, A4R4G4B4 */
//                     p[1] = (sl4 << 4) | (sll << 0);
//                     p[0] = (sll << 4) | (sll << 0);
//                 }
//                 else
//                 {
//                     /* 32-bit, A8R8G8B8 */
//                     byte sll4 = sl4 | (sl4 << 4);
//                     byte sll2 = sll | (sll << 4);
//                     p[0] = sll2;
//                     p[1] = sll2;
//                     p[2] = sll2;
//                     p[3] = sll4;
//                 }
//             }
//         }
//     }
//     sltexture->UnlockRect(0);
// }

static int findedge(D3DLOCKED_RECT* lock, int w, int h, int dx, int dy)
{
    int x = w / 2;
    int y = h / 2;

    if (dx != 0)
        x = dx < 0 ? 0 : w - 1;
    if (dy != 0)
        y = dy < 0 ? 0 : h - 1;

    for (;;)
    {
        uint* p = (uint*)((byte*)lock->pBits + y * lock->Pitch + x * 4);
        int alpha = (*p) >> 24;
        if (alpha != 255)
            break;
        x -= dx;
        y -= dy;
        if (x <= 0 || y <= 0)
            break;
        if (x >= w - 1 || y >= h - 1)
            break;
    }
    if (dx)
        return x;
    return y;
}

// static int createmask2texture(const TCHAR* filename)
// {
//     struct zfile* zf;
//     int size;
//     byte* buf;
//     LPDIRECT3DTEXTURE9 tx;
//     HRESULT hr;
//     D3DXIMAGE_INFO dinfo;
//     TCHAR tmp[MAX_DPATH];
//
//     if (filename[0] == 0 || WIN32GFX_IsPicassoScreen())
//         return 0;
//
//     zf = nullptr;
//     for (int i = 0; i < 2; i++)
//     {
//         if (i == 0)
//         {
//             get_plugin_path(tmp, sizeof tmp / sizeof(TCHAR), L"overlays");
//             _tcscat(tmp, filename);
//         }
//         else
//         {
//             _tcscpy(tmp, filename);
//         }
//         TCHAR tmp2[MAX_DPATH], tmp3[MAX_DPATH];
//         _tcscpy(tmp3, tmp);
//         TCHAR* s = _tcsrchr(tmp3, '.');
//         if (s)
//         {
//             TCHAR* s2 = s;
//             while (s2 > tmp3)
//             {
//                 TCHAR v = *s2;
//                 if (v == '_')
//                 {
//                     s = s2;
//                     break;
//                 }
//                 if (v == 'X' || v == 'x')
//                 {
//                     s2--;
//                     continue;
//                 }
//                 if (!_istdigit(v))
//                     break;
//                 s2--;
//             }
//             _tcscpy(tmp2, s);
//             _stprintf(s, L"_%dx%d%s", window_w, window_h, tmp2);
//             zf = zfile_fopen(tmp3, L"rb", ZFD_NORMAL);
//             if (zf)
//                 break;
//             float aspect = (float)window_w / window_h;
//             int ax = -1, ay = -1;
//             if (abs(aspect - 16.0 / 10.0) <= 0.1)
//                 ax = 16, ay = 10;
//             if (abs(aspect - 16.0 / 9.0) <= 0.1)
//                 ax = 16, ay = 9;
//             if (abs(aspect - 4.0 / 3.0) <= 0.1)
//                 ax = 4, ay = 3;
//             if (ax > 0 && ay > 0)
//             {
//                 _stprintf(s, L"_%dx%d%s", ax, ay, tmp2);
//                 zf = zfile_fopen(tmp3, L"rb", ZFD_NORMAL);
//                 if (zf)
//                     break;
//             }
//         }
//         zf = zfile_fopen(tmp, L"rb", ZFD_NORMAL);
//         if (zf)
//             break;
//     }
//     if (!zf)
//     {
//         Logger::Write(L"%s: couldn't open overlay '%s'\n", D3DHEAD, filename);
//         return 0;
//     }
//     size = zfile_size(zf);
//     buf = xmalloc(byte, size);
//     zfile_fread(buf, size, 1, zf);
//     zfile_fclose(zf);
//     hr = D3DXCreateTextureFromFileInMemoryEx(d3ddev, buf, size,
//                                              D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8,
//                                              D3DPOOL_DEFAULT, D3DX_FILTER_NONE, D3DX_FILTER_NONE, 0, &dinfo, nullptr, &tx);
//     free(buf);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"%s: overlay texture load failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
//         goto end;
//     }
//     mask2texture_w = dinfo.Width;
//     mask2texture_h = dinfo.Height;
//     mask2texture = tx;
//     mask2rect.left = 0;
//     mask2rect.top = 0;
//     mask2rect.right = mask2texture_w;
//     mask2rect.bottom = mask2texture_h;
//
//     D3DLOCKED_RECT lock;
//     if (SUCCEEDED(hr = mask2texture->LockRect(0, &lock, nullptr, 0)))
//     {
//         mask2rect.left = findedge(&lock, mask2texture_w, mask2texture_h, -1, 0);
//         mask2rect.right = findedge(&lock, mask2texture_w, mask2texture_h, 1, 0);
//         mask2rect.top = findedge(&lock, mask2texture_w, mask2texture_h, 0, -1);
//         mask2rect.bottom = findedge(&lock, mask2texture_w, mask2texture_h, 0, 1);
//         mask2texture->UnlockRect(0);
//     }
//     if (mask2rect.left >= mask2texture_w / 2 || mask2rect.top >= mask2texture_h / 2 ||
//         mask2rect.right <= mask2texture_w / 2 || mask2rect.bottom <= mask2texture_h / 2)
//     {
//         mask2rect.left = 0;
//         mask2rect.top = 0;
//         mask2rect.right = mask2texture_w;
//         mask2rect.bottom = mask2texture_h;
//     }
//     mask2texture_multx = (float)window_w / mask2texture_w;
//     mask2texture_multy = (float)window_h / mask2texture_h;
//     mask2texture_offsetw = 0;
//
//     if (isfullscreen() > 0)
//     {
//         struct MultiDisplay* md = getdisplay(&g_curr_conf);
//         float deskw = md->rect.right - md->rect.left;
//         float deskh = md->rect.bottom - md->rect.top;
//         //deskw = 800; deskh = 600;
//         float dstratio = deskw / deskh;
//         float srcratio = mask2texture_w / mask2texture_h;
//         mask2texture_multx *= srcratio / dstratio;
//     }
//     else
//     {
//         mask2texture_multx = mask2texture_multy;
//     }
//
//     mask2texture_wh = window_h;
//     mask2texture_ww = mask2texture_w * mask2texture_multx;
//
//     mask2texture_offsetw = (window_w - mask2texture_ww) / 2;
//
//     blanktexture = createtext(mask2texture_offsetw + 1, window_h, D3DFMT_X8R8G8B8);
//
//     Logger::Write(L"%s: overlay '%s' %.0f*%.0f (%d*%d - %d*%d)\n",
//               D3DHEAD, tmp, mask2texture_w, mask2texture_h,
//               mask2rect.left, mask2rect.top, mask2rect.right, mask2rect.bottom);
//
//     return 1;
// end:
//     if (tx)
//         tx->Release();
//     return 0;
// }

// static int createmasktexture(const TCHAR* filename)
// {
//     int ww = window_w;
//     int hh = window_h;
//     struct zfile* zf;
//     int size;
//     byte* buf;
//     D3DSURFACE_DESC maskdesc, txdesc;
//     LPDIRECT3DTEXTURE9 tx;
//     HRESULT hr;
//     D3DLOCKED_RECT lock, slock;
//     D3DXIMAGE_INFO dinfo;
//     TCHAR tmp[MAX_DPATH];
//
//     if (filename[0] == 0)
//         return 0;
//     tx = nullptr;
//     get_plugin_path(tmp, sizeof tmp / sizeof(TCHAR), L"masks");
//     _tcscat(tmp, filename);
//     zf = zfile_fopen(tmp, L"rb", ZFD_NORMAL);
//     if (!zf)
//     {
//         zf = zfile_fopen(filename, L"rb", ZFD_NORMAL);
//         if (!zf)
//         {
//             Logger::Write(L"%s: couldn't open mask '%s'\n", D3DHEAD, filename);
//             return 0;
//         }
//     }
//     size = zfile_size(zf);
//     buf = xmalloc(byte, size);
//     zfile_fread(buf, size, 1, zf);
//     zfile_fclose(zf);
//     hr = D3DXCreateTextureFromFileInMemoryEx(d3ddev, buf, size,
//                                              D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2, 1, D3DUSAGE_DYNAMIC, D3DFMT_X8R8G8B8,
//                                              D3DPOOL_DEFAULT, D3DX_FILTER_NONE, D3DX_FILTER_NONE, 0, &dinfo, nullptr, &tx);
//     free(buf);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"%s: temp mask texture load failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
//         goto end;
//     }
//     hr = tx->GetLevelDesc(0, &txdesc);
//     if (FAILED(hr))
//     {
//         Logger::Write(L"%s: mask image texture GetLevelDesc() failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
//         goto end;
//     }
//     masktexture_w = dinfo.Width;
//     masktexture_h = dinfo.Height;
//     if (txdesc.Width == masktexture_w && txdesc.Height == masktexture_h && psEnabled)
//     {
//         // texture size == image size, no need to tile it (Wrap sampler does the rest)
//         masktexture = tx;
//         tx = nullptr;
//     }
//     else
//     {
//         masktexture = createtext(ww, hh, D3DFMT_X8R8G8B8);
//         if (FAILED(hr))
//         {
//             Logger::Write(L"%s: mask texture creation failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
//             goto end;
//         }
//         hr = masktexture->GetLevelDesc(0, &maskdesc);
//         if (FAILED(hr))
//         {
//             Logger::Write(L"%s: mask texture GetLevelDesc() failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
//             goto end;
//         }
//         if (SUCCEEDED(hr = masktexture->LockRect(0, &lock, nullptr, 0)))
//         {
//             if (SUCCEEDED(hr = tx->LockRect(0, &slock, nullptr, 0)))
//             {
//                 int x, y, sx, sy;
//                 uint* sptr, * ptr;
//                 sy = 0;
//                 for (y = 0; y < maskdesc.Height; y++)
//                 {
//                     sx = 0;
//                     for (x = 0; x < maskdesc.Width; x++)
//                     {
//                         uint v;
//                         sptr = (uint*)((byte*)slock.pBits + sy * slock.Pitch + sx * 4);
//                         ptr = (uint*)((byte*)lock.pBits + y * lock.Pitch + x * 4);
//                         v = *sptr;
//                         //					v &= 0x00FFFFFF;
//                         //					v |= 0x80000000;
//                         *ptr = v;
//                         sx++;
//                         if (sx >= dinfo.Width)
//                             sx = 0;
//                     }
//                     sy++;
//                     if (sy >= dinfo.Height)
//                         sy = 0;
//                 }
//                 tx->UnlockRect(0);
//             }
//             masktexture->UnlockRect(0);
//         }
//         tx->Release();
//         masktexture_w = maskdesc.Width;
//         masktexture_h = maskdesc.Height;
//     }
//     Logger::Write(L"%s: mask %d*%d (%d*%d) ('%s') texture allocated\n", D3DHEAD, masktexture_w, masktexture_h, txdesc.Width, txdesc.Height, filename);
//     maskmult_x = (float)ww / masktexture_w;
//     maskmult_y = (float)hh / masktexture_h;
//
//     return 1;
// end:
//     if (masktexture)
//         masktexture->Release();
//     masktexture = nullptr;
//     if (tx)
//         tx->Release();
//     return 0;
// }

static void setupscenecoords()
{
    RECT sr, dr, zr;
    float w, h;
    float dw, dh;
    static RECT sr2, dr2, zr2;

    // Logger::Write(L"%dx%d %dx%d %dx%d\n", tin_w, tin_h, tin_w, tin_h, window_w, window_h);

    // float mw = mask2rect.right - mask2rect.left;
    // float mh = mask2rect.bottom - mask2rect.top;

    getfilterrect2(&dr, &sr, &zr, window_w, window_h, tin_w / mult, tin_h / mult, mult, tin_w, tin_h);

    if (memcmp(&sr, &sr2, sizeof RECT) || memcmp(&dr, &dr2, sizeof RECT) || memcmp(&zr, &zr2, sizeof RECT))
    {
        if (Logging::DIRECT3D_DEBUG)
        {
            Logger::Write(L"POS (%d %d %d %d) - (%d %d %d %d)[%d,%d] (%d %d)\n",
                dr.left, dr.top, dr.right, dr.bottom, sr.left, sr.top, sr.right, sr.bottom,
                sr.right - sr.left, sr.bottom - sr.top,
                zr.left, zr.top);
        }
        sr2 = sr;
        dr2 = dr;
        zr2 = zr;
    }

    dw = dr.right - dr.left;
    dh = dr.bottom - dr.top;
    w = sr.right - sr.left;
    h = sr.bottom - sr.top;

    MatrixOrthoOffCenterLH(&m_matProj, 0, w, 0, h, 0.0f, 1.0f);

    float tx, ty;
    float sw, sh;

    // if (mask2texture)
    // {
    //
    //     float winw = mw * mask2texture_multx;
    //     float winh = mh * mask2texture_multy;
    //     dw *= winw / window_w;
    //     dh *= winh / window_h;
    //     tx = -0.5f + dw * tin_w / winw / 2;
    //     ty = +0.5f + dh * tin_h / winh / 2;
    //
    //     float xshift = -zr.left - sr.left;
    //     float yshift = +zr.top + sr.top - (tin_h - h);
    //
    //     sw = dw * tin_w / window_w;
    //     sh = dh * tin_h / window_h;
    //
    //     tx += xshift;
    //     ty += yshift;

    // }
    // else
    // {

    tx = -0.5f + dw * tin_w / window_w / 2;
    ty = +0.5f + dh * tin_h / window_h / 2;

    tx += -zr.left - sr.left;  // - (tin_w - 2 * zr.left - w),
    ty += +zr.top + sr.top - (tin_h - h);

    sw = dw * tin_w / window_w;
    sh = dh * tin_h / window_h;
    // }

    MatrixTranslation(&m_matView, tx, ty, 1.0f);

    MatrixScaling(&m_matWorld, sw, sh, 1.0f);

    cursor_offset_x = -zr.left;
    cursor_offset_y = -zr.top;

    // Logger::Write(L"%.1fx%.1f %.1fx%.1f %.1fx%.1f\n", dw, dh, w, h, sw, sh);

    //  ratio between Amiga texture and overlay mask texture
    float sw2 = dw * tin_w / window_w;
    float sh2 = dh * tin_h / window_h;

    maskmult.x = sw2 / w;
    maskmult.y = sh2 / h;

    maskshift.x = 1.0f;
    maskshift.y = 1.0f;

    D3DXMATRIXA16 tmpmatrix;
    D3DXMatrixMultiply(&tmpmatrix, &m_matWorld, &m_matView);
    D3DXMatrixMultiply(&postproj, &tmpmatrix, &m_matProj);
}

byte* Direct3D::GetFilterBuffer(int* widthp, int* heightp, int* pitch, int* depth)
{
    RECT dr, sr, zr;
    byte* p;
    int w, h;

    *depth = t_depth;
    getfilterrect2(&dr, &sr, &zr, window_w, window_h, tin_w / mult, tin_h / mult, mult, tin_w, tin_h);
    w = sr.right - sr.left;
    h = sr.bottom - sr.top;
    p = gfxvidinfo.bufmem;
    if (pitch)
        *pitch = gfxvidinfo.rowbytes;
    p += (zr.top - h / 2) * gfxvidinfo.rowbytes + (zr.left - w / 2) * t_depth / 8;
    *widthp = w;
    *heightp = h;
    return p;
}

static void createvertex()
{
    HRESULT hr;
    struct TLVERTEX* vertices;
    float sizex, sizey;

    sizex = 1.0f;
    sizey = 1.0f;
    if (FAILED(hr = vertexBuffer->Lock(0, 0, (void**)&vertices, 0)))
    {
        Logger::Write(L"%s: Vertexbuffer lock failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return;
    }
    memset(vertices, 0, sizeof(struct TLVERTEX) * NUMVERTICES);
    // Setup vertices
    vertices[0].position.x = -0.5f;
    vertices[0].position.y = -0.5f;
    vertices[0].diffuse = 0xFFFFFFFF;
    vertices[0].texcoord.x = 0.0f;
    vertices[0].texcoord.y = sizey;
    vertices[1].position.x = -0.5f;
    vertices[1].position.y = 0.5f;
    vertices[1].diffuse = 0xFFFFFFFF;
    vertices[1].texcoord.x = 0.0f;
    vertices[1].texcoord.y = 0.0f;
    vertices[2].position.x = 0.5f;
    vertices[2].position.y = -0.5f;
    vertices[2].diffuse = 0xFFFFFFFF;
    vertices[2].texcoord.x = sizex;
    vertices[2].texcoord.y = sizey;
    vertices[3].position.x = 0.5f;
    vertices[3].position.y = 0.5f;
    vertices[3].diffuse = 0xFFFFFFFF;
    vertices[3].texcoord.x = sizex;
    vertices[3].texcoord.y = 0.0f;
    //  Additional vertices required for some PS effects
    vertices[4].position.x = 0.0f;
    vertices[4].position.y = 0.0f;
    vertices[4].diffuse = 0xFFFFFF00;
    vertices[4].texcoord.x = 0.0f;
    vertices[4].texcoord.y = 1.0f;
    vertices[5].position.x = 0.0f;
    vertices[5].position.y = 1.0f;
    vertices[5].diffuse = 0xFFFFFF00;
    vertices[5].texcoord.x = 0.0f;
    vertices[5].texcoord.y = 0.0f;
    vertices[6].position.x = 1.0f;
    vertices[6].position.y = 0.0f;
    vertices[6].diffuse = 0xFFFFFF00;
    vertices[6].texcoord.x = 1.0f;
    vertices[6].texcoord.y = 1.0f;
    vertices[7].position.x = 1.0f;
    vertices[7].position.y = 1.0f;
    vertices[7].diffuse = 0xFFFFFF00;
    vertices[7].texcoord.x = 1.0f;
    vertices[7].texcoord.y = 0.0f;
    if (FAILED(hr = vertexBuffer->Unlock()))
        Logger::Write(L"%s: Vertexbuffer unlock failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
}

static void settransform()
{
    //  Projection is (0,0,0) -> (1,1,1)
    MatrixOrthoOffCenterLH(&m_matPreProj, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
    //  Align texels with pixels
    MatrixTranslation(&m_matPreView, -0.5f / tin_w, 0.5f / tin_h, 0.0f);
    //  Identity for world
    D3DXMatrixIdentity(&m_matPreWorld);
    psEffect_SetMatrices(&m_matProj, &m_matView, &m_matWorld);

    MatrixOrthoOffCenterLH(&m_matProj2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);

    MatrixTranslation(&m_matView2, 0.5f - 0.5f / window_w, 0.5f + 0.5f / window_h, 0.0f);
    D3DXMatrixIdentity(&m_matWorld2);
}

void Direct3D::InvalidateDeviceObjects()
{
    if (query)
    {
        query->Release();
        query = nullptr;
    }
    if (texture)
    {
        texture->Release();
        texture = nullptr;
    }
    if (sprite)
    {
        sprite->Release();
        sprite = nullptr;
    }
    if (ledtexture)
    {
        ledtexture->Release();
        ledtexture = nullptr;
    }
    // if (sltexture)
    // {
    //     sltexture->Release();
    //     sltexture = nullptr;
    // }
    // if (masktexture)
    // {
    //     masktexture->Release();
    //     masktexture = nullptr;
    // }
    // if (mask2texture)
    // {
    //     mask2texture->Release();
    //     mask2texture = nullptr;
    // }
    // if (blanktexture)
    // {
    //     blanktexture->Release();
    //     blanktexture = nullptr;
    // }
    if (lpTempTexture)
    {
        lpTempTexture->Release();
        lpTempTexture = nullptr;
    }
    if (lpWorkTexture1)
    {
        lpWorkTexture1->Release();
        lpWorkTexture1 = nullptr;
    }
    if (lpWorkTexture2)
    {
        lpWorkTexture2->Release();
        lpWorkTexture2 = nullptr;
    }
    if (lpHq2xLookupTexture)
    {
        lpHq2xLookupTexture->Release();
        lpHq2xLookupTexture = nullptr;
    }
    if (CursorSurface)
    {
        CursorSurface->Release();
        CursorSurface = nullptr;
    }
    if (pEffect)
    {
        pEffect->Release();
        pEffect = nullptr;
    }
    if (postEffect)
    {
        postEffect->Release();
        postEffect = nullptr;
    }
    if (d3ddev)
        d3ddev->SetStreamSource(0, nullptr, 0, 0);
    if (vertexBuffer)
    {
        vertexBuffer->Release();
        vertexBuffer = nullptr;
    }
    m_MatWorldEffectHandle = nullptr;
    m_MatViewEffectHandle = nullptr;
    m_MatProjEffectHandle = nullptr;
    m_MatWorldViewEffectHandle = nullptr;
    m_MatViewProjEffectHandle = nullptr;
    m_MatWorldViewProjEffectHandle = nullptr;
    m_SourceDimsEffectHandle = nullptr;
    m_TexelSizeEffectHandle = nullptr;
    m_SourceTextureEffectHandle = nullptr;
    m_WorkingTexture1EffectHandle = nullptr;
    m_WorkingTexture2EffectHandle = nullptr;
    m_Hq2xLookupTextureHandle = nullptr;
    m_PreprocessTechnique1EffectHandle = nullptr;
    m_PreprocessTechnique2EffectHandle = nullptr;
    m_CombineTechniqueEffectHandle = nullptr;
    locked = 0;
    maskshift.x = maskshift.y = maskshift.z = maskshift.w = 0;
    maskmult.x = maskmult.y = maskmult.z = maskmult.w = 0;
}

int Direct3D::RestoreDeviceObjects()
{
    int vbsize;
    // int wasshader = shaderon;
    HRESULT hr;

    InvalidateDeviceObjects();

    postEffect = psEffect_LoadEffect(L"_posteffect", true);

    if (g_curr_conf.gfx_filtershader != "")
        pEffect = psEffect_LoadEffect(g_curr_conf.gfx_filtershader, false);

    // if (wasshader && !shaderon)
    //     Logger::Write(L"Falling back to non-shader mode\n");

    // createmask2texture(g_curr_conf.gfx_filteroverlay);

    if (!createtexture(tin_w, tin_h, tout_w, tout_h, window_w, window_h))
        return 0;
    createledtexture();

    hr = D3DXCreateSprite(d3ddev, &sprite);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: D3DXSprite failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
    }

    int curw = Direct3D::CursorMaxWidth, curh = Direct3D::CursorMaxHeight;
    CursorSurface = createtext(curw, curh, D3DFMT_A8R8G8B8);
    cursor_v = 0;

    vbsize = sizeof(struct TLVERTEX) * NUMVERTICES;
    if (FAILED(hr = d3ddev->CreateVertexBuffer(vbsize, D3DUSAGE_WRITEONLY,
                D3DFVF_TLVERTEX, D3DPOOL_DEFAULT, &vertexBuffer, nullptr)))
    {
        Logger::Write(L"%s: failed to create vertex buffer: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return 0;
    }
    createvertex();
    if (FAILED(hr = d3ddev->SetFVF(D3DFVF_TLVERTEX)))
        Logger::Write(L"%s: SetFVF failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
    if (FAILED(hr = d3ddev->SetStreamSource(0, vertexBuffer, 0, sizeof(struct TLVERTEX))))
        Logger::Write(L"%s: SetStreamSource failed: %s\n", D3DHEAD, D3D_ErrorString(hr));

    hr = d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
    hr = d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);

    setupscenecoords();
    settransform();

    return 1;
}

void Direct3D::Free_Impl()
{
    InvalidateDeviceObjects();
    if (d3ddev)
    {
        d3ddev->Release();
        d3ddev = nullptr;
    }
    if (d3d)
    {
        d3d->Release();
        d3d = nullptr;
    }
    // d3d_enabled = 0;
    psPreProcess = 0;
    psActive = 0;
    resetcount = 0;
    devicelost = 0;
    g_changed_conf.leds_on_screen = g_curr_conf.leds_on_screen = g_curr_conf.leds_on_screen & ~STATUSLINE_TARGET;
}

void Direct3D::Free()
{
    Free_Impl();
    ddraw_fs_hack_free();
}

static bool getvblankstate(int* vpos)
{
    HRESULT hr;
    D3DRASTER_STATUS rt;

    *vpos = 0;
    if (!isd3d())
        return false;
    hr = d3ddev->GetRasterStatus(0, &rt);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: GetRasterStatus %s\n", D3DHEAD, D3D_ErrorString(hr));
        return false;
    }
    *vpos = rt.ScanLine;
    if (rt.ScanLine > maxscanline)
        maxscanline = rt.ScanLine;
    if (rt.InVBlank != 0)
        *vpos = -1;
    return true;
}

bool Direct3D::WaitVertBlankState(bool state)
{
    int vpos;
    for (;;)
    {
        if (!getvblankstate(&vpos))
            return false;
        while (vpos > maxscanline)
        {
            maxscanline = vpos;
            if (!getvblankstate(&vpos))
                return false;
        }
        if (vpos < 0 || vpos >= maxscanline - 5)
        {
            if (state)
                return true;
        }
        else
        {
            if (!state)
                return true;
        }
    }
}

bool Direct3D::VertBlankBusyWait()
{
    int vpos;

    for (;;)
    {
        if (!getvblankstate(&vpos))
            return false;
        if (vpos <= 0 || vpos >= maxscanline - 5)
            return true;
    }
}

const TCHAR* Direct3D::Init(HWND ahwnd, int w_w, int w_h, int t_w, int t_h, int depth)
{
    HRESULT ret, hr;
    static TCHAR errmsg[100] = { 0 };
    D3DDISPLAYMODE mode;
    D3DCAPS9 d3dCaps;
    int adapter;
    DWORD flags;
    HINSTANCE d3dDLL, d3dx;
    typedef HRESULT (WINAPI * LPDIRECT3DCREATE9EX)(UINT, IDirect3D9Ex**);
    LPDIRECT3DCREATE9EX d3dexp = nullptr;
    bool newvsync = g_curr_conf.gfx_avsync && g_curr_conf.gfx_avsyncmode && !picasso_on;

    Free_Impl();

    // if (!g_curr_conf.gfx_api)
    // {
    //     _tcscpy(errmsg, L"D3D: not enabled");
    //     return errmsg;
    // }

    d3dx = LoadLibrary(D3DX9DLL);
    if (d3dx == nullptr)
    {
        _tcscpy(errmsg, L"Direct3D: Newer DirectX Runtime required.\n\nhttp://go.microsoft.com/fwlink/?linkid=56513");
        if (isfullscreen() <= 0)
            ShellExecute(nullptr, L"open", L"http://go.microsoft.com/fwlink/?linkid=56513", nullptr, nullptr, SW_SHOWNORMAL);
        return errmsg;
    }
    FreeLibrary(d3dx);

    GoodEnough();
    CanShaders();

    d3d_ex = FALSE;
    d3dDLL = LoadLibrary(L"D3D9.DLL");
    if (d3dDLL == nullptr)
    {
        _tcscpy(errmsg, L"Direct3D: DirectX 9 or newer required");
        return errmsg;
    }
    else
    {
        d3dexp = (LPDIRECT3DCREATE9EX)GetProcAddress(d3dDLL, "Direct3DCreate9Ex");
        if (d3dexp)
            d3d_ex = TRUE;
    }
    FreeLibrary(d3dDLL);
    hr = -1;
    if (d3d_ex)
    {
        hr = d3dexp(D3D_SDK_VERSION, &d3dex);
        if (FAILED(hr))
            Logger::Write(L"Direct3D: failed to create D3DEx object: %s\n", D3D_ErrorString(hr));
        d3d = (IDirect3D9*)d3dex;
    }
    if (FAILED(hr))
    {
        d3d_ex = 0;
        d3dex = nullptr;
        d3d = Direct3DCreate9(D3D_SDK_VERSION);
        if (d3d == nullptr)
        {
            Free();
            _tcscpy(errmsg, L"Direct3D: failed to create D3D object");
            return errmsg;
        }
    }
    if (d3d_ex)
        D3DHEAD = L"D3D9Ex";
    else
        D3DHEAD = L"D3D9";

    adapter = g_curr_conf.gfx_display - 1;
    if (adapter < 0)
        adapter = 0;
    if (adapter >= d3d->GetAdapterCount())
        adapter = 0;

    modeex.Size = sizeof modeex;
    if (d3dex)
    {
        LUID luid;
        hr = d3dex->GetAdapterLUID(adapter, &luid);
        hr = d3dex->GetAdapterDisplayModeEx(adapter, &modeex, nullptr);
    }
    if (FAILED(hr = d3d->GetAdapterDisplayMode(adapter, &mode)))
        Logger::Write(L"%s: GetAdapterDisplayMode failed %s\n", D3DHEAD, D3D_ErrorString(hr));
    if (FAILED(hr = d3d->GetDeviceCaps(adapter, D3DDEVTYPE_HAL, &d3dCaps)))
        Logger::Write(L"%s: GetDeviceCaps failed %s\n", D3DHEAD, D3D_ErrorString(hr));

    memset(&dpp, 0, sizeof(dpp));
    dpp.Windowed = isfullscreen() <= 0;
    dpp.BackBufferFormat = mode.Format;
    dpp.BackBufferCount = newvsync ? 0 : (dpp.Windowed || !g_curr_conf.gfx_avsync ? 1 : g_curr_conf.gfx_backbuffers);
    dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
    dpp.BackBufferWidth = w_w;
    dpp.BackBufferHeight = w_h;
    dpp.PresentationInterval = dpp.Windowed || g_curr_conf.gfx_backbuffers == 0 || newvsync ? D3DPRESENT_INTERVAL_IMMEDIATE : D3DPRESENT_INTERVAL_ONE;

    modeex.Width = w_w;
    modeex.Height = w_h;
    modeex.RefreshRate = 0;
    modeex.ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
    modeex.Format = mode.Format;

    vsync2 = 0;
    if (isfullscreen() > 0)
    {
        dpp.FullScreen_RefreshRateInHz = g_curr_conf.gfx_refreshrate > 0 ? g_curr_conf.gfx_refreshrate : 0;
        modeex.RefreshRate = dpp.FullScreen_RefreshRateInHz;
        if (g_curr_conf.gfx_avsync && g_curr_conf.gfx_avsyncmode == 0)
        {
            dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
            if (getvsyncrate(dpp.FullScreen_RefreshRateInHz) != dpp.FullScreen_RefreshRateInHz)
            {
                if (d3dCaps.PresentationIntervals & D3DPRESENT_INTERVAL_TWO)
                    dpp.PresentationInterval = D3DPRESENT_INTERVAL_TWO;
                else
                    vsync2 = 1;
            }
        }
    }

    d3dhwnd = ahwnd;
    t_depth = depth;

    flags = D3DCREATE_FPU_PRESERVE;
    //  Check if hardware vertex processing is available
    if (d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
    {
        flags |= D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE;
    }
    else
    {
        flags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
    }
    if (d3d_ex)
    {
        ret = d3dex->CreateDeviceEx(adapter, D3DDEVTYPE_HAL, d3dhwnd, flags, &dpp, dpp.Windowed ? nullptr : &modeex, &d3ddevex);
        d3ddev = d3ddevex;
    }
    else
    {
        ret = d3d->CreateDevice(adapter, D3DDEVTYPE_HAL, d3dhwnd, flags, &dpp, &d3ddev);
    }
    if (FAILED(ret) && (flags & D3DCREATE_PUREDEVICE))
    {
        flags &= ~D3DCREATE_PUREDEVICE;
        if (d3d_ex)
        {
            ret = d3dex->CreateDeviceEx(adapter, D3DDEVTYPE_HAL, d3dhwnd, flags, &dpp, dpp.Windowed ? nullptr : &modeex, &d3ddevex);
            d3ddev = d3ddevex;
        }
        else
        {
            ret = d3d->CreateDevice(adapter, D3DDEVTYPE_HAL, d3dhwnd, flags, &dpp, &d3ddev);
        }
        if (FAILED(ret) && (flags & D3DCREATE_HARDWARE_VERTEXPROCESSING))
        {
            flags &= ~D3DCREATE_HARDWARE_VERTEXPROCESSING;
            flags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
            if (d3d_ex)
            {
                ret = d3dex->CreateDeviceEx(adapter, D3DDEVTYPE_HAL, d3dhwnd, flags, &dpp, dpp.Windowed ? nullptr : &modeex, &d3ddevex);
                d3ddev = d3ddevex;
            }
            else
            {
                ret = d3d->CreateDevice(adapter, D3DDEVTYPE_HAL, d3dhwnd, flags, &dpp, &d3ddev);
            }
        }
    }

    if (FAILED(ret))
    {
        _stprintf(errmsg, L"%s failed, %s\n", d3d_ex ? L"CreateDeviceEx" : L"CreateDevice", D3D_ErrorString(ret));
        if (ret == D3DERR_INVALIDCALL && dpp.Windowed == 0 && dpp.FullScreen_RefreshRateInHz && !ddraw_fs)
        {
            Logger::Write(L"%s\n", errmsg);
            Logger::Write(L"%s: Retrying fullscreen with DirectDraw\n", D3DHEAD);
            if (ddraw_fs_hack_init())
            {
                const TCHAR* err2 = Init(ahwnd, w_w, w_h, t_w, t_h, depth);
                if (err2)
                    ddraw_fs_hack_free();
                return err2;
            }
        }
        if (d3d_ex)
        {
            Logger::Write(L"D3D Error: %s\n", errmsg);
            // D3DEX = 0;
            return Init(ahwnd, w_w, w_h, t_w, t_h, depth);
        }
        Free();
        return errmsg;
    }

    // if (d3dCaps.PixelShaderVersion >= D3DPS_VERSION(2, 0))
    //     psEnabled = TRUE;
    // else
    //     psEnabled = FALSE;

    max_texture_w = d3dCaps.MaxTextureWidth;
    max_texture_h = d3dCaps.MaxTextureHeight;

    if (Logging::DIRECT3D_DEBUG)
    {
        Logger::Write(L"%s: %08X ", D3DHEAD, flags);
        if (d3dCaps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY)
            Logger::Write(L"SQUAREONLY ");
        if (d3dCaps.TextureCaps & D3DPTEXTURECAPS_POW2)
            Logger::Write(L"POW2 ");
        if (d3dCaps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL)
            Logger::Write(L"NPOTCONDITIONAL ");
        if (d3dCaps.TextureCaps & D3DPTEXTURECAPS_ALPHA)
            Logger::Write(L"ALPHA ");
        if (d3dCaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES)
            Logger::Write(L"DYNAMIC ");
        Logger::Write(L"\n");

        Logger::Write(L"%s: PS=%d.%d VS=%d.%d %d*%d*%d%s%s %d-bit %d\n",
            D3DHEAD,
            (d3dCaps.PixelShaderVersion >> 8) & 0xff, d3dCaps.PixelShaderVersion & 0xff,
            (d3dCaps.VertexShaderVersion >> 8) & 0xff, d3dCaps.VertexShaderVersion & 0xff,
            max_texture_w, max_texture_h,
            dpp.Windowed ? 0 : dpp.FullScreen_RefreshRateInHz,
            dpp.Windowed ? L"" : L" FS",
            g_curr_conf.gfx_avsync ? L" VSYNC" : L"",
            t_depth, adapter
            );
    }

    if ((d3dCaps.PixelShaderVersion < D3DPS_VERSION(2, 0) || max_texture_w < 2048 || max_texture_h < 2048) && d3d_ex)
    {
        // D3DEX = 0;
        Logger::Write(L"Disabling D3D9Ex\n");
        return Init(ahwnd, w_w, w_h, t_w, t_h, depth);
    }

    // if (!shaderon)
    //     Logger::Write(L"Using non-shader version\n");

    // multx = mmult;
    mult = S2X_getmult();
    tin_w = t_w * mult;
    tin_h = t_h * mult;
    tout_w = tin_w;
    tout_h = tin_h;

    if (max_texture_w < w_w || max_texture_h < w_h)
    {
        _stprintf(errmsg, L"%s: %d * %d or bigger texture support required\nYour card's maximum texture size is only %d * %d",
            D3DHEAD, t_w, t_h, max_texture_w, max_texture_h);
        return errmsg;
    }

    // while (multx > 1 && (w_w * multx > max_texture_w || w_h * multx > max_texture_h))
    //     multx--;

    // required_sl_texture_w = w_w;
    // required_sl_texture_h = w_h;
    // if (g_curr_conf.gfx_filter_scanlines > 0 && (max_texture_w < w_w || max_texture_h < w_h))
    // {
    //     gui_message(L"%s: %d * %d or bigger texture support required for scanlines (max is only %d * %d)\n",
    //                 D3DHEAD, L"Scanlines disabled.",
    //                 required_sl_texture_w, required_sl_texture_h, max_texture_w, max_texture_h);
    //     g_changed_conf.gfx_filter_scanlines = g_curr_conf.gfx_filter_scanlines = 0;
    // }

    switch (depth)
    {
        case 32:
        default:
            tformat = D3DFMT_X8R8G8B8;
            break;
        case 15:
            tformat = D3DFMT_X1R5G5B5;
            break;
        case 16:
            tformat = D3DFMT_R5G6B5;
            break;
    }
    window_w = w_w;
    window_h = w_h;

    g_changed_conf.leds_on_screen = g_curr_conf.leds_on_screen = g_curr_conf.leds_on_screen | STATUSLINE_TARGET;

    if (!RestoreDeviceObjects())
    {
        Free();
        _stprintf(errmsg, L"%s: initialization failed.", D3DHEAD);
        return errmsg;
    }
    maxscanline = 0;
    // d3d_enabled = 1;

    if (newvsync)
    {
        d3ddev->CreateQuery(D3DQUERYTYPE_EVENT, &query);
    }
    if (d3ddevex)
        d3ddevex->SetMaximumFrameLatency(1);

    return 0;
}

static HRESULT reset()
{
    if (d3dex)
        return d3ddevex->ResetEx(&dpp, dpp.Windowed ? nullptr : &modeex);
    else
        return d3ddev->Reset(&dpp);
}

int Direct3D::NeedReset()
{
    HRESULT hr;

    if (!devicelost)
        return -1;
    if (d3dex)
        hr = d3ddevex->CheckDeviceState(d3dhwnd);
    else
        hr = d3ddev->TestCooperativeLevel();
    if (hr == S_PRESENT_OCCLUDED)
        return 0;
    if (hr == D3DERR_DEVICENOTRESET)
    {
        Logger::Write(L"%s: DEVICENOTRESET\n", D3DHEAD);
        devicelost = 2;
        InvalidateDeviceObjects();
        hr = reset();
        if (FAILED(hr))
        {
            Logger::Write(L"%s: Reset failed %s\n", D3DHEAD, D3D_ErrorString(hr));
            resetcount++;
            if (resetcount > 2 || hr == D3DERR_DEVICEHUNG)
            {
                // g_changed_conf.gfx_api = 0;
                Logger::Write(L"%s: Too many failed resets, disabling Direct3D mode, wish too... (DDRAW WAS REMOVED)\n", D3DHEAD);
            }
            return 1;
        }
        devicelost = 0;
        Logger::Write(L"%s: Reset succeeded\n", D3DHEAD);
        RestoreDeviceObjects();
        return -1;
    }
    else if (hr == D3DERR_DEVICELOST)
    {
        InvalidateDeviceObjects();
        return 0;
    }
    else if (SUCCEEDED(hr))
    {
        devicelost = 0;
        InvalidateDeviceObjects();
        hr = reset();
        if (FAILED(hr))
            Logger::Write(L"%s: Reset failed %s\n", D3DHEAD, D3D_ErrorString(hr));
        RestoreDeviceObjects();
        return -1;
    }
    Logger::Write(L"%s: TestCooperativeLevel %s\n", D3DHEAD, D3D_ErrorString(hr));
    return 0;
}

// void D3D_clear()
// {
//     int i;
//     HRESULT hr;
//
//     if (!isd3d())
//         return;
//     for (i = 0; i < 2; i++)
//     {
//         hr = d3ddev->Clear(0, nullptr, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, d3ddebug ? 0x80 : 0x00), 0, 0);
//         hr = d3ddev->Present(nullptr, nullptr, nullptr, nullptr);
//     }
// }

void Direct3D::Render()
{
    HRESULT hr;
    LPDIRECT3DTEXTURE9 srctex = texture;
    UINT uPasses, uPass;

    if (!isd3d())
        return;

    hr = d3ddev->Clear(0, nullptr, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 0, 0);

    if (FAILED(hr = d3ddev->BeginScene()))
    {
        Logger::Write(L"%s: BeginScene: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return;
    }
    if (postEffect)
    {
        if (psActive)
        {
            LPDIRECT3DSURFACE9 lpRenderTarget;
            LPDIRECT3DSURFACE9 lpNewRenderTarget;
            LPDIRECT3DTEXTURE9 lpWorkTexture;

            settransform();
            if (!psEffect_SetTextures(texture, lpWorkTexture1, lpWorkTexture2, lpHq2xLookupTexture))
                return;
            if (psPreProcess)
            {
                if (!psEffect_SetMatrices(&m_matPreProj, &m_matPreView, &m_matPreWorld))
                    return;

                if (FAILED(hr = d3ddev->GetRenderTarget(0, &lpRenderTarget)))
                    Logger::Write(L"%s: GetRenderTarget: %s\n", D3DHEAD, D3D_ErrorString(hr));
                lpWorkTexture = lpWorkTexture1;
                lpNewRenderTarget = nullptr;
pass2:
                if (FAILED(hr = lpWorkTexture->GetSurfaceLevel(0, &lpNewRenderTarget)))
                    Logger::Write(L"%s: GetSurfaceLevel: %s\n", D3DHEAD, D3D_ErrorString(hr));
                if (FAILED(hr = d3ddev->SetRenderTarget(0, lpNewRenderTarget)))
                    Logger::Write(L"%s: SetRenderTarget: %s\n", D3DHEAD, D3D_ErrorString(hr));

                uPasses = 0;
                if (psEffect_Begin(pEffect, (lpWorkTexture == lpWorkTexture1) ? psEffect_PreProcess1 : psEffect_PreProcess2, &uPasses))
                {
                    for (uPass = 0; uPass < uPasses; uPass++)
                    {
                        if (psEffect_BeginPass(pEffect, uPass))
                        {
                            if (FAILED(hr = d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2)))
                            {
                                Logger::Write(L"%s: Effect DrawPrimitive failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
                            }
                            psEffect_EndPass(pEffect);
                        }
                    }
                    psEffect_End(pEffect);
                }
                if (FAILED(hr = d3ddev->SetRenderTarget(0, lpRenderTarget)))
                    Logger::Write(L"%s: Effect RenderTarget reset failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
                lpNewRenderTarget->Release();
                lpNewRenderTarget = nullptr;
                if (psEffect_hasPreProcess2() && lpWorkTexture == lpWorkTexture1)
                {
                    lpWorkTexture = lpWorkTexture2;
                    goto pass2;
                }
                lpRenderTarget->Release();
                lpRenderTarget = nullptr;
            }
            psEffect_SetMatrices(&m_matProj2, &m_matView2, &m_matWorld2);

            #if TWOPASS
            if (FAILED(hr = d3ddev->GetRenderTarget(0, &lpRenderTarget)))
                Logger::Write(L"%s: GetRenderTarget: %s\n", D3DHEAD, D3D_ErrorString(hr));
            if (FAILED(hr = lpTempTexture->GetSurfaceLevel(0, &lpNewRenderTarget)))
                Logger::Write(L"%s: GetSurfaceLevel: %s\n", D3DHEAD, D3D_ErrorString(hr));
            if (FAILED(hr = d3ddev->SetRenderTarget(0, lpNewRenderTarget)))
                Logger::Write(L"%s: SetRenderTarget: %s\n", D3DHEAD, D3D_ErrorString(hr));
            #endif
            hr = d3ddev->Clear(0, nullptr, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 0, 0);

            uPasses = 0;
            if (psEffect_Begin(pEffect, psEffect_Combine, &uPasses))
            {
                for (uPass = 0; uPass < uPasses; uPass++)
                {
                    if (!psEffect_BeginPass(pEffect, uPass))
                        return;
                    if (FAILED(hr = d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2)))
                        Logger::Write(L"%s: Effect2 DrawPrimitive failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
                    psEffect_EndPass(pEffect);
                }
                psEffect_End(pEffect);
            }
            #if TWOPASS
            if (FAILED(hr = d3ddev->SetRenderTarget(0, lpRenderTarget)))
                Logger::Write(L"%s: SetRenderTarget: %s\n", D3DHEAD, D3D_ErrorString(hr));
            lpNewRenderTarget->Release();
            lpRenderTarget->Release();
            #endif
            srctex = lpTempTexture;
        }
    }
    #if TWOPASS
    if (postEffect)
    {
        // if (masktexture)
        // {
        //     if (FAILED(hr = postEffect->SetTechnique(postTechnique)))
        //         Logger::Write(L"%s: SetTechnique(postTechnique) failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        //     if (FAILED(hr = postEffect->SetTexture(postMaskTextureHandle, masktexture)))
        //         Logger::Write(L"%s: SetTexture(masktexture) failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        // }
        // else if (sltexture)
        // {
        //     if (FAILED(hr = postEffect->SetTechnique(postTechniqueAlpha)))
        //         Logger::Write(L"%s: SetTechnique(postTechniqueAlpha) failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        //     if (FAILED(hr = postEffect->SetTexture(postMaskTextureHandle, sltexture)))
        //         Logger::Write(L"%s: SetTexture(sltexture) failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        // }
        // else
        // {
        if (FAILED(hr = postEffect->SetTechnique(postTechniquePlain)))
            Logger::Write(L"%s: SetTechnique(postTechniquePlain) failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        // }
        hr = postEffect->SetInt(postFilterMode, D3DTEXF_POINT);

        if (FAILED(hr = postEffect->SetTexture(postSourceTextureHandle, srctex)))
            Logger::Write(L"%s: SetTexture(srctex) failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        setupscenecoords();
        hr = postEffect->SetMatrix(postMatrixSource, &postproj);
        hr = postEffect->SetVector(postMaskMult, &maskmult);
        hr = postEffect->SetVector(postMaskShift, &maskshift);

        uPasses = 0;
        if (psEffect_Begin(postEffect, psEffect_None, &uPasses))
        {
            for (uPass = 0; uPass < uPasses; uPass++)
            {
                if (psEffect_BeginPass(postEffect, uPass))
                {
                    if (FAILED(hr = d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2)))
                        Logger::Write(L"%s: Post DrawPrimitive failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
                    psEffect_EndPass(postEffect);
                }
            }
            psEffect_End(postEffect);
        }
    }
    else
    {
        //  non-shader version
        setupscenecoords();
        hr = d3ddev->SetTransform(D3DTS_PROJECTION, &m_matProj);
        hr = d3ddev->SetTransform(D3DTS_VIEW, &m_matView);
        hr = d3ddev->SetTransform(D3DTS_WORLD, &m_matWorld);
        hr = d3ddev->SetTexture(0, srctex);
        hr = d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

        // int bl = g_curr_conf.gfx_filter_bilinear ? D3DTEXF_LINEAR : D3DTEXF_POINT;

        hr = d3ddev->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
        hr = d3ddev->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);

        // if (sprite && sltexture)
        // {
        //     D3DXVECTOR3 v;
        //     sprite->Begin(D3DXSPRITE_ALPHABLEND);
        //     v.x = v.y = v.z = 0;
        //     sprite->Draw(sltexture, nullptr, nullptr, &v, 0xffffffff);
        //     sprite->End();
        // }
    }

    if (sprite && ((ledtexture) || (CursorSurface && cursor_v)))
    {
        D3DXVECTOR3 v;
        sprite->Begin(D3DXSPRITE_ALPHABLEND);
        if (CursorSurface && cursor_v)
        {
            D3DXMATRIXA16 t;

            MatrixScaling(&t, ((float)(window_w) / (tout_w + 2 * cursor_offset_x)), ((float)(window_h) / (tout_h + 2 * cursor_offset_y)), 0);
            v.x = cursor_x + cursor_offset_x;
            v.y = cursor_y + cursor_offset_y;
            v.z = 0;
            sprite->SetTransform(&t);
            sprite->Draw(CursorSurface, nullptr, nullptr, &v, 0xffffffff);
            MatrixScaling(&t, 1, 1, 0);
            sprite->Flush();
            sprite->SetTransform(&t);
        }

        // if (mask2texture)
        // {
        //     D3DXMATRIXA16 t;
        //     RECT r;
        //     float srcw = mask2texture_w;
        //     float srch = mask2texture_h;
        //     float aspectsrc = srcw / srch;
        //     float aspectdst = (float)window_w / window_h;
        //     float w, h;
        //
        //     w = mask2texture_multx;
        //     h = mask2texture_multy;
        //     #if 0
        //     if (g_curr_conf.gfx_filteroverlay_pos.width > 0)
        //         w = (float)g_curr_conf.gfx_filteroverlay_pos.width / srcw;
        //     else if (g_curr_conf.gfx_filteroverlay_pos.width == -1)
        //         w = 1.0;
        //     else if (g_curr_conf.gfx_filteroverlay_pos.width <= -24000)
        //         w = w * (-g_curr_conf.gfx_filteroverlay_pos.width - 30000) / 100.0;
        //
        //     if (g_curr_conf.gfx_filteroverlay_pos.height > 0)
        //         h = (float)g_curr_conf.gfx_filteroverlay_pos.height / srch;
        //     else if (g_curr_conf.gfx_filteroverlay_pos.height == -1)
        //         h = 1;
        //     else if (g_curr_conf.gfx_filteroverlay_pos.height <= -24000)
        //         h = h * (-g_curr_conf.gfx_filteroverlay_pos.height - 30000) / 100.0;
        //     #endif
        //     MatrixScaling(&t, w, h, 0);
        //
        //     v.x = 0;
        //     if (g_curr_conf.gfx_filteroverlay_pos.x == -1)
        //         v.x = (window_w - (mask2texture_w * w)) / 2;
        //     else if (g_curr_conf.gfx_filteroverlay_pos.x > -24000)
        //         v.x = g_curr_conf.gfx_filteroverlay_pos.x;
        //     else
        //         v.x = (window_w - (mask2texture_w * w)) / 2 + (-g_curr_conf.gfx_filteroverlay_pos.x - 30100) * window_w / 100.0;
        //
        //     v.y = 0;
        //     if (g_curr_conf.gfx_filteroverlay_pos.y == -1)
        //         v.y = (window_h - (mask2texture_h * h)) / 2;
        //     else if (g_curr_conf.gfx_filteroverlay_pos.y > -24000)
        //         v.y = g_curr_conf.gfx_filteroverlay_pos.y;
        //     else
        //         v.y = (window_h - (mask2texture_h * h)) / 2 + (-g_curr_conf.gfx_filteroverlay_pos.y - 30100) * window_h / 100.0;
        //
        //     v.x /= w;
        //     v.y /= h;
        //     v.x = v.y = 0;
        //     v.z = 0;
        //     v.x += mask2texture_offsetw / w;
        //
        //     r.left = 0;
        //     r.top = 0;
        //     r.right = mask2texture_w;
        //     r.bottom = mask2texture_h;
        //
        //     sprite->SetTransform(&t);
        //     sprite->Draw(mask2texture, &r, nullptr, &v, 0xffffffff);
        //     sprite->Flush();
        //     MatrixScaling(&t, 1, 1, 0);
        //     sprite->SetTransform(&t);
        //
        //     if (mask2texture_offsetw > 0)
        //     {
        //         v.x = 0;
        //         v.y = 0;
        //         r.left = 0;
        //         r.top = 0;
        //         r.right = mask2texture_offsetw + 1;
        //         r.bottom = window_h;
        //         sprite->Draw(blanktexture, &r, nullptr, &v, 0xffffffff);
        //         if (window_w > mask2texture_offsetw + mask2texture_ww)
        //         {
        //             v.x = mask2texture_offsetw + mask2texture_ww;
        //             v.y = 0;
        //             r.left = 0;
        //             r.top = 0;
        //             r.right = window_w - (mask2texture_offsetw + mask2texture_ww) + 1;
        //             r.bottom = window_h;
        //             sprite->Draw(blanktexture, &r, nullptr, &v, 0xffffffff);
        //         }
        //     }
        //
        // }

        if (ledtexture && (((g_curr_conf.leds_on_screen & STATUSLINE_RTG) && WIN32GFX_IsPicassoScreen()) || ((g_curr_conf.leds_on_screen & STATUSLINE_CHIPSET) && !WIN32GFX_IsPicassoScreen())))
        {
            int slx, sly;
            statusline_getpos(&slx, &sly, window_w, window_h);
            v.x = slx;
            v.y = sly;
            v.z = 0;
            sprite->Draw(ledtexture, nullptr, nullptr, &v, 0xffffffff);
        }
        sprite->End();
    }
    #endif

    hr = d3ddev->EndScene();
    if (FAILED(hr))
        Logger::Write(L"%s: EndScene() %s\n", D3DHEAD, D3D_ErrorString(hr));
}

void Direct3D::SetCursor(int x, int y, int visible)
{
    cursor_x = x;
    cursor_y = y;
    cursor_v = visible;
}

void Direct3D::UnlockTexture()
{
    HRESULT hr;

    if (!isd3d())
        return;
    if (g_curr_conf.leds_on_screen & (STATUSLINE_CHIPSET | STATUSLINE_RTG))
        updateleds();

    if (locked)
        hr = texture->UnlockRect(0);
    locked = 0;
    fulllocked = 0;
}

void Direct3D::FlushTexture(int miny, int maxy)
{
    if (fulllocked)
        return;
    if (miny >= 0 && maxy >= 0)
    {
        RECT r;
        maxy++;
        r.left = 0;
        r.right = tin_w;
        r.top = miny <= 0 ? 0 : miny;
        r.bottom = maxy <= tin_h ? maxy : tin_h;
        if (r.top <= r.bottom)
        {
            HRESULT hr = texture->AddDirtyRect(&r);
            if (FAILED(hr))
                Logger::Write(L"%s: AddDirtyRect(): %s\n", D3DHEAD, D3D_ErrorString(hr));
            // Logger::Write (L"%d %d\n", r.top, r.bottom);
        }
    }
}

byte* Direct3D::LockTexture(int* pitch, int fullupdate)
{
    D3DLOCKED_RECT lock;
    HRESULT hr;

    if (NeedReset() > 0)
        return nullptr;
    if (!isd3d())
        return nullptr;

    lock.pBits = nullptr;
    lock.Pitch = 0;
    hr = texture->LockRect(0, &lock, nullptr, fullupdate ? D3DLOCK_DISCARD : D3DLOCK_NO_DIRTY_UPDATE);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: LockRect failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return nullptr;
    }
    if (lock.pBits == nullptr || lock.Pitch == 0)
    {
        Logger::Write(L"%s: LockRect returned nullptr texture\n", D3DHEAD);
        UnlockTexture();
        return nullptr;
    }
    locked = 1;
    fulllocked = fullupdate;
    *pitch = lock.Pitch;
    return (byte*)lock.pBits;
}

bool Direct3D::RenderFrame()
{
    static int frameskip;

    if (!isd3d())
        return false;
    if (g_curr_conf.turbo_emulation && isfullscreen() > 0 && frameskip-- > 0)
        return false;
    frameskip = 50;

    Render();

    if (vsync2 && !g_curr_conf.turbo_emulation)
    {
        Render();
    }

    if (query)
    {
        if (SUCCEEDED(query->Issue(D3DISSUE_END)))
        {
            while (query->GetData(nullptr, 0, D3DGETDATA_FLUSH) == S_FALSE)
                ;
        }
    }
    return true;
}

void Direct3D::ShowFrame()
{
    HRESULT hr;

    if (!isd3d())
        return;
    hr = d3ddev->Present(nullptr, nullptr, nullptr, nullptr);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: Present() %s\n", D3DHEAD, D3D_ErrorString(hr));
        if (hr == D3DERR_DEVICELOST)
        {
            devicelost = 1;
        }
    }
}

void Direct3D::Refresh()
{
    if (!isd3d())
        return;
    Render();
    ShowFrame();
    Render();
    ShowFrame();
    // createscanlines(0);
}

void Direct3D::GetPixelFormat(int depth, int* rb, int* gb, int* bb, int* rs, int* gs, int* bs, int* ab, int* as, int* a)
{
    switch (depth)
    {
        case 32:
            *rb = 8;
            *gb = 8;
            *bb = 8;
            *ab = 8;
            *rs = 16;
            *gs = 8;
            *bs = 0;
            *as = 24;
            *a = 0;
            break;
        case 15:
            *rb = 5;
            *gb = 5;
            *bb = 5;
            *ab = 1;
            *rs = 10;
            *gs = 5;
            *bs = 0;
            *as = 15;
            *a = 0;
            break;
        case 16:
            *rb = 5;
            *gb = 6;
            *bb = 5;
            *ab = 0;
            *rs = 11;
            *gs = 5;
            *bs = 0;
            *as = 0;
            *a = 0;
            break;
    }
}

double Direct3D::GetRefreshRate()
{
    HRESULT hr;
    D3DDISPLAYMODE dmode;
    if (!isd3d())
        return -1;
    hr = d3ddev->GetDisplayMode(0, &dmode);
    if (FAILED(hr))
        return -1;
    return dmode.RefreshRate;
}

void Direct3D::GUIMode(int guion)
{
    HRESULT hr;
    if (!isd3d())
        return;
    hr = d3ddev->SetDialogBoxMode(guion);
    if (FAILED(hr))
        Logger::Write(L"%s: SetDialogBoxMode %s\n", D3DHEAD, D3D_ErrorString(hr));
    guimode = guion;
}

HDC Direct3D::GetDC()
{
    // static LPDIRECT3DSURFACE9 bb;
    HRESULT hr;
    HDC hdc;

    if (!isd3d())
        return 0;
    hr = d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &hdc_bb);
    if (FAILED(hr))
    {
        Logger::Write(L"%s: GetBackBuffer() failed: %s\n", D3DHEAD, D3D_ErrorString(hr));
        return 0;
    }
    hr = hdc_bb->GetDC(&hdc);
    if (SUCCEEDED(hr))
        return hdc;
    Logger::Write(L"%s: GetDC() failed: %s\n", D3DHEAD, D3D_ErrorString(hr));

    if (hdc)
        hdc_bb->ReleaseDC(hdc);
    hdc_bb->Release();
    hdc_bb = nullptr;
    return 0;
}

// int D3D_isenabled()
// {
//     return d3d_enabled;
// }

HDC Direct3D::ReleaseDC(HDC hdc)
{
    if (!isd3d())
        return 0;

    if (hdc)
        hdc_bb->ReleaseDC(hdc);
    hdc_bb->Release();
    hdc_bb = nullptr;
    return 0;
}

bool Direct3D::IsVerticalBlank()
{
    D3DRASTER_STATUS status;
    HRESULT hr = d3ddev->GetRasterStatus(0, &status);
    if (FAILED(hr))
        Logger::Write(L"%s: GetRasterStatus %s\n", D3DHEAD, D3D_ErrorString(hr));
    return status.InVBlank == TRUE;
}

// #endif